diff --git a/playwright/e2e/workflow.spec.ts b/playwright/e2e/workflow.spec.ts index ec2bc823..89d87c76 100644 --- a/playwright/e2e/workflow.spec.ts +++ b/playwright/e2e/workflow.spec.ts @@ -12,6 +12,8 @@ const adminPassword = process.env.NEXTCLOUD_ADMIN_PASSWORD ?? 'admin' const escapeRegex = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +const workflowRuleSelector = '.section.rule, [class^="_rule_"]' + const selectNcOption = async(page: Page, combobox: Locator, optionName: string) => { await combobox.click() await page.locator('[role="option"]').filter({ @@ -19,8 +21,8 @@ const selectNcOption = async(page: Page, combobox: Locator, optionName: string) }).first().click() } -const ensureFlowCardIsVisible = async(page: Page, addFlowCard: Locator) => { - if (await addFlowCard.count() > 0) { +const ensureFlowCardIsVisible = async(page: Page, addFlowButton: Locator) => { + if (await addFlowButton.count() > 0) { return } @@ -29,28 +31,28 @@ const ensureFlowCardIsVisible = async(page: Page, addFlowCard: Locator) => { await showMoreButton.click() } - await expect(addFlowCard).toBeVisible() + await expect(addFlowButton).toBeVisible() } const configureDraftRule = async(page: Page, actionName: string, label: string, fieldValue: string, configureOperation?: (configuredRule: Locator) => Promise, operationValue?: string) => { - const initialRuleCount = await page.locator('.section.rule').count() - const addFlowCard = page.locator('.actions__item.colored').filter({ - has: page.getByRole('heading', { name: actionName, exact: true }), - }) - await ensureFlowCardIsVisible(page, addFlowCard) - await addFlowCard.getByRole('button', { name: 'Add new flow' }).click() - - const configuredRule = page.locator('.section.rule').filter({ + const initialRuleCount = await page.locator(workflowRuleSelector).count() + const addFlowButton = page.getByRole('heading', { name: actionName, exact: true }) + .locator('..') + .getByRole('button', { name: 'Add new flow' }) + await ensureFlowCardIsVisible(page, addFlowButton) + await addFlowButton.click() + + const configuredRule = page.locator(workflowRuleSelector).filter({ has: page.getByRole('button', { name: 'Cancel', exact: true }), }).last() await expect(configuredRule).toBeVisible() - const configuredRuleIndex = await configuredRule.evaluate((element) => { - return Array.from(document.querySelectorAll('.section.rule')).indexOf(element) - }) + const configuredRuleIndex = await configuredRule.evaluate((element, selector) => { + return Array.from(document.querySelectorAll(selector)).indexOf(element) + }, workflowRuleSelector) await expect(configuredRule.getByText('Profile field value updated', { exact: true })).toBeVisible() - await selectNcOption(page, configuredRule.getByRole('combobox', { name: 'Select a filter' }), 'Profile field value') - await selectNcOption(page, configuredRule.locator('.comparator [role="combobox"]'), 'is') + await selectNcOption(page, configuredRule.getByRole('combobox', { name: /^(Filter|Select a filter)$/ }), 'Profile field value') + await selectNcOption(page, configuredRule.getByRole('combobox', { name: /^(Comparator|Select a comparator)$/ }), 'is') const checkEditor = configuredRule.locator('oca-profile-fields-check-user-profile-field') await expect(checkEditor).toBeVisible() @@ -70,8 +72,8 @@ const configureDraftRule = async(page: Page, actionName: string, label: string, await expect(configuredRule.getByRole('button', { name: 'Save' })).toBeVisible() await configuredRule.getByRole('button', { name: 'Save' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount + 1) - const savedRule = page.locator('.section.rule').nth(configuredRuleIndex) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount + 1) + const savedRule = page.locator(workflowRuleSelector).nth(configuredRuleIndex) await expect(savedRule.getByText('Profile field value updated', { exact: true })).toBeVisible() await expect(savedRule.getByText('Active', { exact: true })).toBeVisible() @@ -92,6 +94,17 @@ test.beforeEach(async ({ page }) => { await login(page.request, adminUser, adminPassword) }) +test('admin sees profile field flow icons in the card text color', async ({ page }) => { + await page.goto('./settings/admin/workflow') + const heading = page.getByRole('heading', { name: 'Log profile field change', exact: true }).first() + await expect(heading).toBeVisible() + + const icon = heading.locator('xpath=../../*[1]') + await expect(icon).toHaveCSS('filter', 'none') + await expect(icon).toHaveCSS('background-image', 'none') + await expect(icon).toHaveCSS('mask-image', /url\(/) +}) + test('admin can create a profile field workflow rule', async ({ page }) => { const suffix = Date.now() const fieldKey = `playwright_workflow_${suffix}` @@ -105,7 +118,7 @@ test('admin can create a profile field workflow rule', async ({ page }) => { const { savedRule, initialRuleCount } = await configureDraftRule(page, 'Log profile field change', label, fieldValue) await savedRule.getByRole('button', { name: 'Delete' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount) await deleteDefinitionByFieldKey(page.request, fieldKey) }) @@ -126,7 +139,7 @@ test('admin can create a send webhook workflow rule', async ({ page }) => { }, webhookUrl) await savedRule.getByRole('button', { name: 'Delete' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount) await deleteDefinitionByFieldKey(page.request, fieldKey) }) @@ -146,7 +159,7 @@ test('admin can create an email affected user workflow rule', async ({ page }) = }) await savedRule.getByRole('button', { name: 'Delete' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount) await deleteDefinitionByFieldKey(page.request, fieldKey) }) @@ -172,7 +185,7 @@ test('admin can create a notify admins or groups workflow rule', async ({ page } }) await savedRule.getByRole('button', { name: 'Delete' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount) await deleteDefinitionByFieldKey(page.request, fieldKey) }) @@ -189,6 +202,6 @@ test('admin can create a create Talk conversation workflow rule', async ({ page const { savedRule, initialRuleCount } = await configureDraftRule(page, 'Create Talk conversation', label, fieldValue) await savedRule.getByRole('button', { name: 'Delete' }).click() - await expect(page.locator('.section.rule')).toHaveCount(initialRuleCount) + await expect(page.locator(workflowRuleSelector)).toHaveCount(initialRuleCount) await deleteDefinitionByFieldKey(page.request, fieldKey) }) diff --git a/src/tests/utils/workflowEngineStore.spec.ts b/src/tests/utils/workflowEngineStore.spec.ts new file mode 100644 index 00000000..06d6ec17 --- /dev/null +++ b/src/tests/utils/workflowEngineStore.spec.ts @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { describe, expect, it, vi } from 'vitest' + +import { + applyDefaultTriggerToCreatedRule, + getWorkflowEngineStore, + type WorkflowEngineRule, + type WorkflowEngineStore, +} from '../../utils/workflowEngineStore.ts' + +const entityClass = 'OCA\\ProfileFields\\Workflow\\ProfileFieldValueEntity' +const eventClass = 'OCA\\ProfileFields\\Workflow\\Event\\ProfileFieldValueUpdatedEvent' +const operationClass = 'OCA\\ProfileFields\\Workflow\\LogProfileFieldChangeOperation' +const fileEntityClass = 'OCA\\WorkflowEngine\\Entity\\File' + +const defaults = { + entityClass, + eventClass, + operationClasses: [operationClass], +} + +const entities = [ + { id: fileEntityClass, events: [{ eventName: 'postCreate', displayName: 'File created' }] }, + { + id: entityClass, + events: [ + { eventName: 'otherEvent', displayName: 'Other' }, + { eventName: eventClass, displayName: 'Profile field value updated' }, + ], + }, +] + +const createRule = (overrides: Partial = {}): WorkflowEngineRule => ({ + id: -1, + class: operationClass, + entity: fileEntityClass, + events: ['postCreate'], + ...overrides, +}) + +const createStore = (): WorkflowEngineStore => ({ + getEntities: () => entities, + setRuleTrigger: vi.fn(), + onRuleCreated: vi.fn(), +}) + +describe('getWorkflowEngineStore', () => { + it('returns null without a workflow engine root', () => { + expect(getWorkflowEngineStore(null)).toBeNull() + }) + + it('returns null when the root has no mounted store', () => { + expect(getWorkflowEngineStore(document.createElement('div'))).toBeNull() + }) + + it('adapts the Vuex store of the Vue 2 workflow engine', () => { + const rule = createRule() + const state = { rules: [createRule({ id: 7 }), rule], entities } + let subscriber: ((mutation: { type: string }, mutationState: typeof state) => void) | undefined + const vuexStore = { + state, + commit: vi.fn(), + subscribe: vi.fn((handler: (mutation: { type: string }, mutationState: typeof state) => void) => { + subscriber = handler + }), + } + const root = Object.assign(document.createElement('div'), { __vue__: { $store: vuexStore } }) + + const store = getWorkflowEngineStore(root) + const callback = vi.fn() + store?.onRuleCreated(callback) + subscriber?.({ type: 'updateRule' }, state) + subscriber?.({ type: 'addRule' }, state) + store?.setRuleTrigger(rule, entityClass, [eventClass]) + + expect(store?.getEntities()).toBe(entities) + expect(callback).toHaveBeenCalledOnce() + expect(callback).toHaveBeenCalledWith(rule) + expect(vuexStore.commit).toHaveBeenCalledWith('updateRule', { ...rule, entity: entityClass, events: [eventClass] }) + }) + + it('adapts the Pinia store of the Vue 3 workflow engine', () => { + const rule = createRule() + let actionHandler: ((context: { name: string, after: (callback: () => void) => void }) => void) | undefined + const piniaStore = { + entities, + rules: [createRule({ id: 7 }), rule], + setRuleTrigger: vi.fn(), + $onAction: vi.fn((handler: (context: { name: string, after: (callback: () => void) => void }) => void) => { + actionHandler = handler + }), + } + const root = Object.assign(document.createElement('div'), { + __vue_app__: { config: { globalProperties: { $pinia: { _s: new Map([['workflowengine', piniaStore]]) } } } }, + }) + + const store = getWorkflowEngineStore(root) + const callback = vi.fn() + store?.onRuleCreated(callback) + actionHandler?.({ name: 'updateRule', after: (afterCallback) => afterCallback() }) + actionHandler?.({ name: 'createNewRule', after: (afterCallback) => afterCallback() }) + store?.setRuleTrigger(rule, entityClass, [eventClass]) + + expect(store?.getEntities()).toBe(entities) + expect(callback).toHaveBeenCalledOnce() + expect(callback).toHaveBeenCalledWith(rule) + expect(piniaStore.setRuleTrigger).toHaveBeenCalledWith(rule, entityClass, [eventClass]) + }) + + it('ignores a Pinia instance without the workflow engine store', () => { + const root = Object.assign(document.createElement('div'), { + __vue_app__: { config: { globalProperties: { $pinia: { _s: new Map([['other', {}]]) } } } }, + }) + + expect(getWorkflowEngineStore(root)).toBeNull() + }) +}) + +describe('applyDefaultTriggerToCreatedRule', () => { + it('points a new profile field rule at the profile field update event', () => { + const rule = createRule() + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, rule, defaults) + + expect(store.setRuleTrigger).toHaveBeenCalledOnce() + expect(store.setRuleTrigger).toHaveBeenCalledWith(rule, entityClass, [eventClass]) + }) + + it('ignores rules of other operations so profile field drafts keep their trigger', () => { + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, createRule({ class: 'OCA\\FilesAccessControl\\Operation' }), defaults) + + expect(store.setRuleTrigger).not.toHaveBeenCalled() + }) + + it('ignores saved rules', () => { + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, createRule({ id: 3 }), defaults) + + expect(store.setRuleTrigger).not.toHaveBeenCalled() + }) + + it('keeps a rule that already uses the default trigger', () => { + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, createRule({ entity: entityClass, events: [eventClass] }), defaults) + + expect(store.setRuleTrigger).not.toHaveBeenCalled() + }) + + it('falls back to the first event of the entity', () => { + const rule = createRule() + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, rule, { ...defaults, eventClass: 'missingEvent' }) + + expect(store.setRuleTrigger).toHaveBeenCalledWith(rule, entityClass, ['otherEvent']) + }) + + it('does nothing when the profile field entity is not available', () => { + const store = createStore() + + applyDefaultTriggerToCreatedRule(store, createRule(), { ...defaults, entityClass: 'missingEntity' }) + + expect(store.setRuleTrigger).not.toHaveBeenCalled() + }) +}) diff --git a/src/utils/workflowEngineStore.ts b/src/utils/workflowEngineStore.ts new file mode 100644 index 00000000..38a13979 --- /dev/null +++ b/src/utils/workflowEngineStore.ts @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +export type WorkflowEngineEntity = { + id: string + events: Array<{ + eventName: string + displayName: string + }> +} + +export type WorkflowEngineRule = { + id: number + class: string + entity: string + events: string[] +} + +export type WorkflowEngineStore = { + getEntities: () => WorkflowEngineEntity[] + setRuleTrigger: (rule: WorkflowEngineRule, entity: string, events: string[]) => void + onRuleCreated: (callback: (rule: WorkflowEngineRule) => void) => void +} + +export type WorkflowRuleDefaults = { + entityClass: string + eventClass: string + operationClasses: string[] +} + +type VuexWorkflowStore = { + state: { + rules: WorkflowEngineRule[] + entities: WorkflowEngineEntity[] + } + commit: (type: string, payload?: unknown) => void + subscribe: (handler: (mutation: { type: string }, state: VuexWorkflowStore['state']) => void) => unknown +} + +type PiniaActionContext = { + name: string + after: (callback: () => void) => void +} + +type PiniaWorkflowStore = { + entities: WorkflowEngineEntity[] + rules: WorkflowEngineRule[] + setRuleTrigger: (rule: WorkflowEngineRule, entity: string, events: string[]) => void + $onAction: (handler: (context: PiniaActionContext) => void) => unknown +} + +type WorkflowEngineRootElement = Element & { + __vue__?: { $store?: VuexWorkflowStore } + __vue_app__?: { config?: { globalProperties?: { $pinia?: { _s?: Map } } } } +} + +const piniaStoreId = 'workflowengine' + +const createVuexAdapter = (store: VuexWorkflowStore): WorkflowEngineStore => ({ + getEntities: () => store.state.entities, + setRuleTrigger: (rule, entity, events) => store.commit('updateRule', { ...rule, entity, events }), + onRuleCreated: (callback) => { + store.subscribe((mutation, state) => { + const createdRule = state.rules.at(-1) + if (mutation.type === 'addRule' && createdRule !== undefined) { + callback(createdRule) + } + }) + }, +}) + +const createPiniaAdapter = (store: PiniaWorkflowStore): WorkflowEngineStore => ({ + getEntities: () => store.entities, + setRuleTrigger: (rule, entity, events) => store.setRuleTrigger(rule, entity, events), + onRuleCreated: (callback) => { + store.$onAction(({ name, after }) => { + if (name !== 'createNewRule') { + return + } + + after(() => { + const createdRule = store.rules.at(-1) + if (createdRule !== undefined) { + callback(createdRule) + } + }) + }) + }, +}) + +const isPiniaWorkflowStore = (store: unknown): store is PiniaWorkflowStore => { + return typeof store === 'object' + && store !== null + && typeof (store as PiniaWorkflowStore).$onAction === 'function' + && typeof (store as PiniaWorkflowStore).setRuleTrigger === 'function' +} + +export const getWorkflowEngineStore = (root: Element | null): WorkflowEngineStore | null => { + if (root === null) { + return null + } + + const workflowRoot = root as WorkflowEngineRootElement + const vuexStore = workflowRoot.__vue__?.$store + if (vuexStore !== undefined) { + return createVuexAdapter(vuexStore) + } + + const piniaStore = workflowRoot.__vue_app__?.config?.globalProperties?.$pinia?._s?.get(piniaStoreId) + if (isPiniaWorkflowStore(piniaStore)) { + return createPiniaAdapter(piniaStore) + } + + return null +} + +const getDefaultEventName = (store: WorkflowEngineStore, defaults: WorkflowRuleDefaults): string | null => { + const entity = store.getEntities().find((item) => item.id === defaults.entityClass) + if (entity === undefined) { + return null + } + + return entity.events.find((event) => event.eventName === defaults.eventClass)?.eventName + ?? entity.events[0]?.eventName + ?? null +} + +export const applyDefaultTriggerToCreatedRule = (store: WorkflowEngineStore, rule: WorkflowEngineRule, defaults: WorkflowRuleDefaults): void => { + if (!defaults.operationClasses.includes(rule.class) || rule.id >= 0) { + return + } + + const defaultEventName = getDefaultEventName(store, defaults) + if (defaultEventName === null) { + return + } + + if (rule.entity === defaults.entityClass && rule.events.length === 1 && rule.events[0] === defaultEventName) { + return + } + + store.setRuleTrigger(rule, defaults.entityClass, [defaultEventName]) +} diff --git a/src/workflow.ts b/src/workflow.ts index 4c79c48f..9a08cea2 100644 --- a/src/workflow.ts +++ b/src/workflow.ts @@ -7,6 +7,7 @@ import { createApp, h, reactive, type App } from 'vue' import { listDefinitions, searchWorkflowTargetSuggestions, type WorkflowTargetSuggestion } from './api.ts' import type { FieldDefinition } from './types/index.ts' +import { applyDefaultTriggerToCreatedRule, getWorkflowEngineStore } from './utils/workflowEngineStore.ts' import { getWorkflowOperatorKeys, isWorkflowOperatorSupported, @@ -72,39 +73,6 @@ type WorkflowEngineApi = { registerOperator: (plugin: WorkflowEngineOperatorPlugin) => void } -type WorkflowEngineEntity = { - id: string - events: Array<{ - eventName: string - displayName: string - }> -} - -type WorkflowEngineRule = { - id: number - class: string - entity: string - events: string[] - name: string - checks: Array<{ class: string | null, operator: string | null, value: string }> - operation: string - valid?: boolean -} - -type WorkflowEngineStore = { - state: { - rules: WorkflowEngineRule[] - entities: WorkflowEngineEntity[] - } - commit: (type: string, payload?: WorkflowEngineRule) => void - dispatch: (type: string, payload?: unknown) => Promise | unknown -} - -type WorkflowEngineRootVm = { - $store: WorkflowEngineStore - createNewRule: (operation: { id: string }) => Promise | unknown -} - const workflowCheckClass = 'OCA\\ProfileFields\\Workflow\\UserProfileFieldCheck' const workflowOperationClasses = [ 'OCA\\ProfileFields\\Workflow\\LogProfileFieldChangeOperation', @@ -126,8 +94,9 @@ const workflowOperationNames = new Set([ t('profile_fields', 'Create Talk conversation'), t('profile_fields', 'Send webhook'), ]) -const workflowCardClassName = 'profile-fields-workflow-card' const workflowItemClassName = 'profile-fields-workflow-item' +const workflowDescriptionClassName = 'profile-fields-workflow-description' +const workflowIconClassName = 'profile-fields-workflow-icon' const workflowCardThemeStyleId = 'profile-fields-workflow-card-theme' // TRANSLATORS The labels below are technical workflow operators shown in filter dropdowns. @@ -1133,21 +1102,17 @@ const ensureWorkflowCardThemeStyle = (): void => { const style = document.createElement('style') style.id = workflowCardThemeStyleId style.textContent = ` - .actions__item.${workflowItemClassName} { - color: var(--color-main-text); - } - - .actions__item.${workflowItemClassName} .actions__item__description h3, - .actions__item.${workflowItemClassName} .actions__item__description small, - .actions__item.${workflowItemClassName} .actions__item__description { - color: var(--color-main-text); + .${workflowItemClassName}, + .${workflowItemClassName} .${workflowDescriptionClassName}, + .${workflowItemClassName} h3 { + color: var(--color-main-text) !important; } - .actions__item.${workflowItemClassName} .actions__item__description small { - color: color-mix(in srgb, var(--color-main-text) 78%, transparent); + .${workflowItemClassName} small { + color: color-mix(in srgb, var(--color-main-text) 78%, transparent) !important; } - .actions__item.${workflowItemClassName} .icon { + .${workflowItemClassName} .${workflowIconClassName} { background-color: currentColor; background-image: none !important; mask-image: var(--profile-fields-workflow-icon); @@ -1165,48 +1130,34 @@ const ensureWorkflowCardThemeStyle = (): void => { document.head.append(style) } -const isWorkflowOperationCard = (element: Element): boolean => { - const heading = element.querySelector('.actions__item__description h3') - return workflowOperationNames.has(heading?.textContent?.trim() ?? '') -} - const applyWorkflowCardTheme = (): void => { ensureWorkflowCardThemeStyle() - for (const card of document.querySelectorAll('.actions__item')) { - if (!isWorkflowOperationCard(card)) { + for (const heading of document.querySelectorAll('h3')) { + if (!workflowOperationNames.has(heading.textContent?.trim() ?? '')) { continue } - card.classList.add(workflowItemClassName) - if (card.classList.contains('colored')) { - card.classList.add(workflowCardClassName) + const description = heading.parentElement + const card = description?.parentElement + const icon = card?.firstElementChild + if (!(description instanceof HTMLElement) || !(card instanceof HTMLElement) || !(icon instanceof HTMLElement) || icon === description) { + continue } - const icon = card.querySelector('.icon') - const backgroundImage = icon?.style.backgroundImage || (icon === null || icon === undefined ? '' : window.getComputedStyle(icon).backgroundImage) - if (icon !== null && backgroundImage !== '' && backgroundImage !== 'none') { - icon.style.setProperty('--profile-fields-workflow-icon', backgroundImage) - } + card.classList.add(workflowItemClassName) + description.classList.add(workflowDescriptionClassName) + icon.classList.add(workflowIconClassName) - const addFlowButton = card.querySelector('button') - if (card.classList.contains('colored') && addFlowButton !== null && addFlowButton.dataset.profileFieldsWorkflowTriggerBound !== 'true') { - addFlowButton.dataset.profileFieldsWorkflowTriggerBound = 'true' - addFlowButton.addEventListener('click', () => { - window.setTimeout(() => { - const store = getWorkflowStore() - if (store !== null) { - applyDefaultTriggerToNewestWorkflowRule(store) - } - }, 0) - }) + const backgroundImage = icon.style.backgroundImage || window.getComputedStyle(icon).backgroundImage + if (backgroundImage !== '' && backgroundImage !== 'none') { + icon.style.setProperty('--profile-fields-workflow-icon', backgroundImage) } } } let workflowCardThemeObserver: MutationObserver | null = null -let workflowDefaultsPatchAttempts = 0 -let workflowDefaultsPatched = false +let workflowRuleDefaultsAttempts = 0 const observeWorkflowCards = (): void => { applyWorkflowCardTheme() @@ -1234,74 +1185,30 @@ const startWorkflowCardTheme = (): void => { window.setTimeout(() => observeWorkflowCards(), 0) } -const getWorkflowRootVm = (): WorkflowEngineRootVm | null => { - const root = document.querySelector('#workflowengine') as (HTMLElement & { __vue__?: WorkflowEngineRootVm }) | null - return root?.__vue__ ?? null +const workflowRuleDefaults = { + entityClass: workflowEntityClass, + eventClass: workflowUpdatedEventClass, + operationClasses: workflowOperationClasses, } -const getWorkflowStore = (): WorkflowEngineStore | null => { - return getWorkflowRootVm()?.$store ?? null -} - -const getDefaultWorkflowEventName = (store: WorkflowEngineStore): string | null => { - const entity = store.state.entities.find((item) => item.id === workflowEntityClass) - if (entity === undefined) { - return null - } - - return entity.events.find((event) => event.eventName === workflowUpdatedEventClass)?.eventName - ?? entity.events[0]?.eventName - ?? null -} - -const applyDefaultTriggerToNewestWorkflowRule = (store: WorkflowEngineStore): void => { - const defaultEventName = getDefaultWorkflowEventName(store) - if (defaultEventName === null) { - return - } - - const targetRule = [...store.state.rules] - .reverse() - .find((rule) => workflowOperationClasses.includes(rule.class) && rule.id < 0) - - if (targetRule === undefined) { +const applyWorkflowRuleDefaults = (): void => { + const store = getWorkflowEngineStore(document.querySelector('#workflowengine')) + if (store !== null) { + store.onRuleCreated((rule) => applyDefaultTriggerToCreatedRule(store, rule, workflowRuleDefaults)) return } - if (targetRule.entity === workflowEntityClass && targetRule.events.length === 1 && targetRule.events[0] === defaultEventName) { - return - } - - store.commit('updateRule', { - ...targetRule, - entity: workflowEntityClass, - events: [defaultEventName], - }) -} - -const patchWorkflowCreateRuleDefaults = (): void => { - if (workflowDefaultsPatched) { - return - } - - const store = getWorkflowStore() - const rootVm = getWorkflowRootVm() - if (store === null || rootVm === null) { - if (workflowDefaultsPatchAttempts >= 20) { - return - } - - workflowDefaultsPatchAttempts += 1 - window.setTimeout(patchWorkflowCreateRuleDefaults, 50) + if (workflowRuleDefaultsAttempts >= 20) { return } - workflowDefaultsPatched = true + workflowRuleDefaultsAttempts += 1 + window.setTimeout(applyWorkflowRuleDefaults, 50) } void loadDefinitions() startWorkflowCardTheme() -patchWorkflowCreateRuleDefaults() +applyWorkflowRuleDefaults() let registrationAttempts = 0