diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/Plugin.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/Plugin.tsx index 0e86eb4bf..096341e8f 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/Plugin.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/Plugin.tsx @@ -4,7 +4,6 @@ import { Registry } from '@tachybase/utils/client'; import { WorkflowLink } from './components/WorkflowLink'; import { ExecutionPage } from './ExecutionPage'; -import { PluginActionTrigger } from './features/action-trigger'; import { PluginAggregate } from './features/aggregate'; import PluginApproval from './features/approval'; import PluginWorkflowDataMappingClient from './features/data-mapping'; @@ -86,7 +85,6 @@ export class PluginWorkflow extends Plugin { await this.pm.add(PluginDaynamicCalculation); await this.pm.add(PluginDelay); await this.pm.add(PluginAggregate); - await this.pm.add(PluginActionTrigger); await this.pm.add(PluginWorkflowJsonParseClient); await this.pm.add(PluginWorkflowJSParseClient); await this.pm.add(PluginWorkflowDataMappingClient); diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/ActionTrigger.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/ActionTrigger.tsx deleted file mode 100644 index a9eda0d0b..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/ActionTrigger.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { - SchemaInitializerItemType, - useCollectionDataSource, - useCollectionManager_deprecated, - useCompile, -} from '@tachybase/client'; -import { useForm } from '@tachybase/schema'; - -import { CollectionBlockInitializer } from '../../components'; -import { useWorkflowAnyExecuted } from '../../hooks'; -import { lang, NAMESPACE, tval } from '../../locale'; -import { Trigger } from '../../triggers'; -import { getCollectionFieldOptions } from '../../variable'; - -export class ActionTrigger extends Trigger { - title = tval('Action event(deprecated)'); - description = `{{t("Triggers after specific operations on data are submitted, such as create, update, delete, etc., or directly submitting a record to the workflow.", { ns: "${NAMESPACE}" })}}`; - fieldset = { - collection: { - type: 'string', - required: true, - 'x-decorator': 'FormItem', - 'x-component': 'DataSourceCollectionCascader', - 'x-disabled': '{{ useWorkflowAnyExecuted() }}', - title: `{{t("Collection", { ns: "${NAMESPACE}" })}}`, - description: `{{t("Which collection record belongs to.", { ns: "${NAMESPACE}" })}}`, - 'x-reactions': [ - { - target: 'appends', - effects: ['onFieldValueChange'], - fulfill: { - state: { - value: [], - }, - }, - }, - ], - }, - appends: { - type: 'array', - title: `{{t("Associations to use", { ns: "${NAMESPACE}" })}}`, - description: `{{t("Please select the associated fields that need to be accessed in subsequent nodes. With more than two levels of to-many associations may cause performance issue, please use with caution.", { ns: "workflow" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'AppendsTreeSelect', - 'x-component-props': { - title: 'Preload associations', - multiple: true, - useCollection() { - const { values } = useForm(); - return values?.collection; - }, - }, - 'x-reactions': [ - { - dependencies: ['collection'], - fulfill: { - state: { - visible: '{{!!$deps[0]}}', - }, - }, - }, - ], - }, - }; - scope = { - useCollectionDataSource, - useWorkflowAnyExecuted, - }; - isActionTriggerable = (config, context) => { - return ['create', 'update', 'customize:update', 'customize:triggerWorkflows'].includes(context.action); - }; - useVariables(config, options) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const compile = useCompile(); - // eslint-disable-next-line react-hooks/rules-of-hooks - const { getCollectionFields } = useCollectionManager_deprecated(); - // eslint-disable-next-line react-hooks/rules-of-hooks - const langTriggerData = lang('Trigger data'); - // eslint-disable-next-line react-hooks/rules-of-hooks - const langUserSubmittedForm = lang('User submitted action'); - // eslint-disable-next-line react-hooks/rules-of-hooks - const langRoleSubmittedForm = lang('Role of user submitted action'); - const rootFields = [ - { - collectionName: config.collection, - name: 'data', - type: 'hasOne', - target: config.collection, - uiSchema: { - title: langTriggerData, - }, - }, - { - collectionName: 'users', - name: 'user', - type: 'hasOne', - target: 'users', - uiSchema: { - title: langUserSubmittedForm, - }, - }, - { - name: 'roleName', - uiSchema: { - title: langRoleSubmittedForm, - }, - }, - ]; - const result = getCollectionFieldOptions({ - // depth, - appends: ['data', 'user', ...(config.appends?.map((item) => `data.${item}`) || [])], - ...options, - fields: rootFields, - compile, - getCollectionFields, - }); - return result; - } - useInitializers(config): SchemaInitializerItemType | null { - if (!config.collection) { - return null; - } - - return { - name: 'triggerData', - type: 'item', - key: 'triggerData', - title: `{{t("Trigger data", { ns: "${NAMESPACE}" })}}`, - Component: CollectionBlockInitializer, - collection: config.collection, - dataPath: '$context.data', - }; - } -} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/configuration.test.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/configuration.test.ts deleted file mode 100644 index 9cce730d2..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/configuration.test.ts +++ /dev/null @@ -1,676 +0,0 @@ -import { - apiCreateRecordTriggerFormEvent, - apiCreateWorkflow, - apiDeleteWorkflow, - apiGetDataSourceCount, - apiGetWorkflow, - apiUpdateWorkflowTrigger, - appendJsonCollectionName, - FormEventTriggerNode, - generalWithNoRelationalFields, - WorkflowListRecords, -} from '@tachybase/plugin-workflow-test/e2e'; -import { expect, test } from '@tachybase/test/e2e'; -import { dayjs } from '@tachybase/utils'; - -import { faker } from '@faker-js/faker'; - -test.describe('Configuration page to configure the Trigger node', () => { - test('Form Submit Button Binding Workflow Add Data Trigger', async ({ - page, - mockPage, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - //配置工作流触发器 - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); - await formEventTriggerNode.nodeConfigure.click(); - await formEventTriggerNode.collectionDropDown.click(); - await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); - await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await formEventTriggerNode.submitButton.click(); - - //配置录入数据区块 - const newPage = mockPage(); - await newPage.goto(); - await page.waitForLoadState('networkidle'); - await page.getByLabel('schema-initializer-Grid-page:addBlock').hover(); - await page.getByRole('menuitem', { name: 'table Table' }).hover(); - const dataSourcesCount = await apiGetDataSourceCount(); - if (dataSourcesCount > 1) { - await page.getByRole('menuitem', { name: 'Main right' }).hover(); - } - await page.getByRole('menuitem', { name: `${triggerNodeCollectionDisplayName}` }).click(); - - // 移开鼠标,关闭菜单 - await page.mouse.move(300, 0); - - await page.getByText('Configure columns').hover(); - await page.getByText(triggerNodeFieldDisplayName).click(); - await page.getByText('Configure actions').hover(); - await page.getByRole('menuitem', { name: 'Add new' }).getByRole('switch').click(); - await expect(page.getByRole('menuitem', { name: 'Add new' }).getByRole('switch')).toBeEnabled(); - await page.getByLabel(`action-Action-Add new-create-${triggerNodeCollectionName}-table`).click(); - await page.getByLabel(`schema-initializer-Grid-popup:addNew:addBlock-${triggerNodeCollectionName}`).hover(); - await page.getByRole('menuitem', { name: 'form Form' }).click(); - // 移开鼠标,关闭菜单 - await page.mouse.move(300, 0); - await page - .getByLabel(`schema-initializer-ActionBar-createForm:configureActions-${triggerNodeCollectionName}`) - .hover(); - await page.getByRole('menuitem', { name: 'Submit' }).click(); - // 绑定工作流 - await page.getByLabel(`schema-initializer-Grid-form:configureFields-${triggerNodeCollectionName}`).hover(); - await page.getByRole('menuitem', { name: `${triggerNodeFieldDisplayName}` }).click(); - await page.mouse.move(300, 0); - await page.getByLabel(`action-Action-Submit-submit-${triggerNodeCollectionName}-form`).hover(); - await page - .getByRole('button', { - name: `designer-schema-settings-Action-actionSettings:createSubmit-${triggerNodeCollectionName}`, - }) - .hover(); - await page.getByRole('menuitem', { name: 'Bind workflows' }).click(); - await page.getByRole('button', { name: 'plus Add workflow' }).click(); - await page.getByRole('button', { name: 'Select workflow' }).click(); - await page.getByRole('option', { name: workFlowName }).click(); - await page.getByRole('button', { name: 'Submit', exact: true }).click(); - - // 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮 - const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - await page.getByRole('textbox').fill(fieldData); - await page.getByLabel(`action-Action-Submit-submit-${triggerNodeCollectionName}-form`, { exact: true }).click(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - // 3、预期结果:触发次数为1 - const getWorkflow = await apiGetWorkflow(workflowId); - const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - const getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(1); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); - - test('Form Submit to Workflow Button Add Data Trigger', async ({ page, mockPage, mockCollections, mockRecords }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - //配置工作流触发器 - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); - await formEventTriggerNode.nodeConfigure.click(); - await formEventTriggerNode.collectionDropDown.click(); - await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); - await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await formEventTriggerNode.submitButton.click(); - - //配置录入数据区块 - const newPage = mockPage(); - await newPage.goto(); - await page.waitForLoadState('networkidle'); - await page.getByLabel('schema-initializer-Grid-page:addBlock').hover(); - await page.getByRole('menuitem', { name: 'table Table' }).hover(); - const dataSourcesCount = await apiGetDataSourceCount(); - if (dataSourcesCount > 1) { - await page.getByRole('menuitem', { name: 'Main right' }).hover(); - } - await page.getByRole('menuitem', { name: `${triggerNodeCollectionDisplayName}` }).click(); - - // 移开鼠标,关闭菜单 - await page.mouse.move(300, 0); - - await page.getByText('Configure columns').hover(); - await page.getByText(triggerNodeFieldDisplayName).click(); - await page.getByText('Configure actions').hover(); - await page.getByRole('menuitem', { name: 'Add new' }).getByRole('switch').click(); - await expect(page.getByRole('menuitem', { name: 'Add new' }).getByRole('switch')).toBeEnabled(); - - await page.getByLabel(`action-Action-Add new-create-${triggerNodeCollectionName}-table`).click(); - await page.getByLabel(`schema-initializer-Grid-popup:addNew:addBlock-${triggerNodeCollectionName}`).hover(); - await page.getByRole('menuitem', { name: 'form Form' }).click(); - - // 移开鼠标,关闭菜单 - await page.mouse.move(300, 0); - - // 绑定工作流 - await page - .getByLabel(`schema-initializer-ActionBar-createForm:configureActions-${triggerNodeCollectionName}`) - .hover(); - await page.getByRole('menuitem', { name: 'Customize' }).hover(); - await page.getByRole('menuitem', { name: 'Submit to workflow' }).click(); - await page.getByLabel(`schema-initializer-Grid-form:configureFields-${triggerNodeCollectionName}`).hover(); - await page.getByRole('menuitem', { name: `${triggerNodeFieldDisplayName}` }).click(); - await page.mouse.move(300, 0); - await page.getByRole('button', { name: 'Submit to workflow' }).hover(); - await page - .getByRole('button', { - name: `designer-schema-settings-Action-Action.Designer-${triggerNodeCollectionName}`, - }) - .hover(); - await page.getByRole('menuitem', { name: 'Bind workflows' }).click(); - await page.getByRole('button', { name: 'plus Add workflow' }).click(); - await page.getByRole('button', { name: 'Select workflow' }).click(); - await page.getByRole('option', { name: workFlowName }).click(); - await page.getByRole('button', { name: 'Submit', exact: true }).click(); - - // 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮 - const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - await page.getByRole('textbox').fill(fieldData); - await page - .getByLabel(`action-Action-Submit to workflow-customize:triggerWorkflows-${triggerNodeCollectionName}-form`) - .click(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - // 3、预期结果:触发次数为1 - const getWorkflow = await apiGetWorkflow(workflowId); - const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - const getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(1); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Configuration Page Path Jump Workflow Management Page', () => { - test('Action event Workflow Configuration Page Path Jump Workflow Management Page', async ({ - page, - mockPage, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - // const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - // const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - // const triggerNodeFieldName = 'orgname'; - // const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - // await mockCollections(appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText).collections); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - //配置工作流触发器 - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - - // 2、测试步骤:跳转到工作流管理页面 - await page.getByRole('link', { name: 'Workflow' }).click(); - - // 3、预期结果:跳转路径正确 - await page.waitForLoadState('networkidle'); - expect(page.url()).toBe(`${process.env.APP_BASE_URL}/admin/settings/workflow`); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); - - test('Action event Workflow History Version Configuration Page Path Jump Workflow Management Page', async ({ - page, - mockPage, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - await page.getByLabel('more').click(); - await page.getByLabel('revision').click(); - await page.waitForLoadState('networkidle'); - //元素重复 - await page.getByLabel('version', { exact: true }).click(); - await page.getByLabel('version-1').click(); - await page.getByRole('link', { name: 'Workflow' }).click(); - - // 3、预期结果:跳转路径正确 - await page.waitForLoadState('networkidle'); - expect(page.url()).toBe(`${process.env.APP_BASE_URL}/admin/settings/workflow`); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); - - test('Action event Workflow Execution Log Page Path Jump Workflow Management Page', async ({ - page, - mockPage, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - const workflowListRecords = new WorkflowListRecords(page, workFlowName); - await workflowListRecords.executionCountPopup.click(); - await page.getByText('View').click(); - await page.waitForLoadState('networkidle'); - await page.getByRole('link', { name: 'Workflow', exact: true }).click(); - - // 3、预期结果:跳转路径正确 - await page.waitForLoadState('networkidle'); - expect(page.url()).toBe(`${process.env.APP_BASE_URL}/admin/settings/workflow`); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); - - test.skip('Action event Workflow Execution Log Page Path Jump Execution Log Screen', async ({ - page, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - const workflowListRecords = new WorkflowListRecords(page, workFlowName); - await workflowListRecords.executionCountPopup.click(); - await page.getByText('View').click(); - await page.waitForLoadState('networkidle'); - //跳转其他执行日志界面,元素无法定位 - - // 3、预期结果:跳转路径正确 - await page.waitForLoadState('networkidle'); - // expect(page.url()).toBe(`${process.env.APP_BASE_URL}/admin/settings/workflow`); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Configuration page version switching', () => {}); - -test.describe('Configuration page disable enable', () => { - test('Action event Workflow Add Data Trigger Disable Do Not Trigger', async ({ - page, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: false, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - // 3、预期结果:触发次数为1 - let getWorkflow = await apiGetWorkflow(workflowId); - let getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - let getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(0); - - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - await page.getByRole('switch', { name: 'On Off' }).click(); - - await apiCreateRecordTriggerFormEvent(triggerNodeCollectionName, triggerWorkflows, { - orgname: triggerNodeCollectionRecordOne + '1', - }); - await page.waitForTimeout(1000); - - getWorkflow = await apiGetWorkflow(workflowId); - getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(1); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); - - test('Action event Workflow Add Data Trigger Disable Enable Post Trigger', async ({ - page, - mockCollections, - mockRecords, - }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - // 3、预期结果:触发次数为1 - let getWorkflow = await apiGetWorkflow(workflowId); - let getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - let getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(1); - - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - await page.getByRole('switch', { name: 'On Off' }).click(); - - await apiCreateRecordTriggerFormEvent(triggerNodeCollectionName, triggerWorkflows, { - orgname: triggerNodeCollectionRecordOne + '1', - }); - await page.waitForTimeout(1000); - - getWorkflow = await apiGetWorkflow(workflowId); - getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow)); - getWorkflowExecuted = getWorkflowObj.executed; - expect(getWorkflowExecuted).toBe(1); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Configuration page execution history', () => {}); - -test.describe('Configuration page copy to new version', () => { - test('Copy the action event of the Configuration Trigger node', async ({ page, mockCollections, mockRecords }) => { - //数据表后缀标识 - const triggerNodeAppendText = faker.string.alphanumeric(5); - - //创建触发器节点数据表 - const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`; - const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`; - const triggerNodeFieldName = 'orgname'; - const triggerNodeFieldDisplayName = '公司名称(单行文本)'; - await mockCollections( - appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText) - .collections, - ); - //添加工作流 - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - const WorkflowKey = workflowObj.key; - - //配置工作流触发器 - const triggerNodeData = { config: { collection: triggerNodeCollectionName, appends: [] } }; - const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData); - const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode)); - - // 2、测试步骤:添加数据触发工作流 - const triggerNodeCollectionRecordOne = - triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); - const triggerWorkflows = WorkflowKey; - const triggerNodeCollectionRecords = await apiCreateRecordTriggerFormEvent( - triggerNodeCollectionName, - triggerWorkflows, - { orgname: triggerNodeCollectionRecordOne }, - ); - await page.waitForTimeout(1000); - - await page.goto(`admin/workflow/workflows/${workflowId}`); - await page.waitForLoadState('networkidle'); - await page.getByLabel('more').click(); - await page.getByLabel('revision').click(); - await page.waitForLoadState('networkidle'); - - // 3、预期结果:新版本工作流配置内容同旧版本一样 - const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); - await formEventTriggerNode.nodeConfigure.click(); - await expect( - page - .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') - .getByText(`Main / ${triggerNodeCollectionDisplayName}`), - ).toBeVisible(); - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Configuration page delete version', () => {}); - -test.describe('Node Add Modify Delete', () => {}); diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/workflowCRUD.test.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/workflowCRUD.test.ts deleted file mode 100644 index c45814934..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/__e2e__/workflowCRUD.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - apiCreateRecordTriggerFormEvent, - apiCreateWorkflow, - apiDeleteWorkflow, - apiGetWorkflow, - apiUpdateWorkflowTrigger, - appendJsonCollectionName, - CreateWorkFlow, - EditWorkFlow, - FormEventTriggerNode, - generalWithNoRelationalFields, - WorkflowListRecords, -} from '@tachybase/plugin-workflow-test/e2e'; -import { expect, test } from '@tachybase/test/e2e'; -import { dayjs } from '@tachybase/utils'; - -import { faker } from '@faker-js/faker'; - -test.describe('Filter', () => { - test('filter workflow name', async ({ page }) => { - //添加工作流 - const triggerNodeAppendText = faker.string.alphanumeric(5); - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - // 2、筛选工作流 - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - await page.getByLabel('action-Filter.Action-Filter-filter-workflows').click(); - await page.getByRole('textbox').fill(workFlowName); - await page.getByRole('button', { name: 'Submit' }).click(); - - // 3、预期结果:列表中出现筛选的工作流 - await expect(page.getByText(workFlowName)).toBeAttached(); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Add new', () => { - test('add new Action event', async ({ page }) => { - // 添加工作流 - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - await page.getByLabel('action-Action-Add new-workflows').click(); - const createWorkFlow = new CreateWorkFlow(page); - const workFlowName = faker.string.alphanumeric(5); - await createWorkFlow.name.fill(workFlowName); - await createWorkFlow.triggerType.click(); - await page.getByRole('option', { name: 'Action event' }).click(); - await page.getByLabel('action-Action-Submit-workflows').click(); - - // 3、预期结果:列表中出现新建的工作流 - await expect(page.getByText(workFlowName)).toBeVisible(); - - // 4、后置处理:删除工作流 - await page.getByLabel('action-Filter.Action-Filter-filter-workflows').click(); - await page.getByRole('textbox').fill(workFlowName); - await page.getByRole('button', { name: 'Submit' }).click(); - await page.getByLabel(`action-Action.Link-Delete-workflows-${workFlowName}`).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); - await expect(page.getByText(workFlowName)).toBeHidden(); - }); -}); - -test.describe('Sync', () => {}); - -test.describe('Delete', () => { - test('delete Action event', async ({ page }) => { - //添加工作流 - const triggerNodeAppendText = faker.string.alphanumeric(5); - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - // 删除工作流 - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - await page.getByLabel('action-Filter.Action-Filter-filter-workflows').click(); - await page.getByRole('textbox').fill(workFlowName); - await page.getByRole('button', { name: 'Submit' }).click(); - await page.getByLabel(`action-Action.Link-Delete-workflows-${workFlowName}`).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); - - // 3、预期结果:列表中出现筛选的工作流 - await expect(page.getByText(workFlowName)).toBeHidden(); - - // 4、后置处理:删除工作流 - }); -}); - -test.describe('Edit', () => { - test('edit Action event name', async ({ page }) => { - //添加工作流 - const triggerNodeAppendText = faker.string.alphanumeric(5); - let workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - // 编辑工作流 - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - await page.getByLabel(`action-Action.Link-Edit-workflows-${workFlowName}`).click(); - const editWorkFlow = new EditWorkFlow(page, workFlowName); - workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - await editWorkFlow.name.fill(workFlowName); - await page.getByLabel('action-Action-Submit-workflows').click(); - await page.waitForLoadState('networkidle'); - // 3、预期结果:编辑成功,列表中出现编辑后的工作流 - await expect(page.getByText(workFlowName)).toBeAttached(); - - // 4、后置处理:删除工作流 - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Duplicate', () => { - test('Duplicate Action event triggers with only unconfigured trigger nodes', async ({ page }) => { - //添加工作流 - const triggerNodeAppendText = faker.string.alphanumeric(5); - const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText; - const workflowData = { - current: true, - options: { deleteExecutionOnStatus: [] }, - title: workFlowName, - type: 'action', - enabled: true, - }; - const workflow = await apiCreateWorkflow(workflowData); - const workflowObj = JSON.parse(JSON.stringify(workflow)); - const workflowId = workflowObj.id; - - // 2、复制工作流 - await page.goto('/admin/settings/workflow'); - await page.waitForLoadState('networkidle'); - await page.getByLabel(`action-Action.Link-Duplicate-workflows-${workFlowName}`).click(); - await page.getByLabel(`action-Action-Submit-workflows-${workFlowName}`).click(); - await page.waitForLoadState('networkidle'); - // 3、预期结果:列表中出现筛选的工作流 - await page.getByLabel('action-Filter.Action-Filter-filter-workflows').click(); - await page.getByRole('textbox').fill(workFlowName); - await page.getByRole('button', { name: 'Submit', exact: true }).click(); - await expect(page.getByText(`${workFlowName} copy`)).toBeAttached(); - - // 4、后置处理:删除工作流 - await page.getByLabel(`action-Action.Link-Delete-workflows-${workFlowName} copy`).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); - await expect(page.getByText(`${workFlowName} copy`)).toBeHidden(); - await apiDeleteWorkflow(workflowId); - }); -}); - -test.describe('Executed', () => {}); diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/index.ts deleted file mode 100644 index e7f1840f5..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/action-trigger/index.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Plugin, SchemaInitializerItemType } from '@tachybase/client'; - -import WorkflowPlugin, { useRecordTriggerWorkflowsActionProps, useTriggerWorkflowsActionProps } from '../..'; -import { ActionTrigger } from './ActionTrigger'; - -const submitToWorkflowActionInitializer: SchemaInitializerItemType = { - name: 'submitToWorkflow', - title: '{{t("Submit to workflow", { ns: "workflow" })}}', - Component: 'CustomizeActionInitializer', - schema: { - title: '{{t("Submit to workflow", { ns: "workflow" })}}', - 'x-component': 'Action', - 'x-use-component-props': 'useTriggerWorkflowsActionProps', - 'x-designer': 'Action.Designer', - 'x-action-settings': { - // assignedValues: {}, - skipValidator: false, - onSuccess: { - manualClose: true, - redirecting: false, - successMessage: '{{t("Submitted successfully")}}', - }, - triggerWorkflows: [], - }, - 'x-action': 'customize:triggerWorkflows', - }, -}; - -const recordTriggerWorkflowActionInitializer: SchemaInitializerItemType = { - name: 'submitToWorkflow', - title: '{{t("Submit to workflow", { ns: "workflow" })}}', - Component: 'CustomizeActionInitializer', - schema: { - title: '{{t("Submit to workflow", { ns: "workflow" })}}', - 'x-component': 'Action', - 'x-use-component-props': 'useRecordTriggerWorkflowsActionProps', - 'x-designer': 'Action.Designer', - 'x-action-settings': { - onSuccess: { - manualClose: true, - redirecting: false, - successMessage: '{{t("Submitted successfully")}}', - }, - triggerWorkflows: [], - }, - 'x-action': 'customize:triggerWorkflows', - }, -}; - -const recordTriggerWorkflowActionLinkInitializer = { - ...recordTriggerWorkflowActionInitializer, - schema: { - ...recordTriggerWorkflowActionInitializer.schema, - 'x-component': 'Action.Link', - }, -}; - -export class PluginActionTrigger extends Plugin { - async load() { - const workflow = this.app.pm.get('workflow') as WorkflowPlugin; - workflow.registerTrigger('action', ActionTrigger); - - this.app.addScopes({ - useTriggerWorkflowsActionProps, - useRecordTriggerWorkflowsActionProps, - }); - - const FormActionInitializers = this.app.schemaInitializerManager.get('FormActionInitializers'); - FormActionInitializers.add('customize.submitToWorkflow', submitToWorkflowActionInitializer); - - const CreateFormActionInitializers = this.app.schemaInitializerManager.get('createForm:configureActions'); - CreateFormActionInitializers.add('customize.submitToWorkflow', submitToWorkflowActionInitializer); - - const UpdateFormActionInitializers = this.app.schemaInitializerManager.get('editForm:configureActions'); - UpdateFormActionInitializers.add('customize.submitToWorkflow', submitToWorkflowActionInitializer); - - const DetailsActionInitializers = this.app.schemaInitializerManager.get('detailsWithPaging:configureActions'); - DetailsActionInitializers.add('customize.submitToWorkflow', recordTriggerWorkflowActionInitializer); - - const ReadPrettyFormActionInitializers = this.app.schemaInitializerManager.get('details:configureActions'); - ReadPrettyFormActionInitializers.add('customize.submitToWorkflow', recordTriggerWorkflowActionInitializer); - - const TableActionColumnInitializers = this.app.schemaInitializerManager.get('table:configureItemActions'); - TableActionColumnInitializers.add('customize.submitToWorkflow', recordTriggerWorkflowActionLinkInitializer); - - const GridCardItemActionInitializers = this.app.schemaInitializerManager.get('gridCard:configureItemActions'); - GridCardItemActionInitializers.add('customize.submitToWorkflow', recordTriggerWorkflowActionLinkInitializer); - - const ListItemActionInitializers = this.app.schemaInitializerManager.get('list:configureItemActions'); - ListItemActionInitializers.add('customize.submitToWorkflow', recordTriggerWorkflowActionLinkInitializer); - } -} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/locale/ko_KR.json b/packages/plugins/@tachybase/plugin-workflow/src/locale/ko_KR.json index 90d769662..0ce76c502 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/locale/ko_KR.json +++ b/packages/plugins/@tachybase/plugin-workflow/src/locale/ko_KR.json @@ -28,7 +28,6 @@ "Workflow will be triggered after submitting succeeded.": "제출 성공 후 워크플로우가 트리거됩니다.", "Workflow will be triggered after saving succeeded.": "저장 성공 후 워크플로우가 트리거됩니다.", "Workflow will be triggered directly once the button is clicked, without data saving.": "버튼을 클릭하면 데이터 저장 없이 워크플로우가 직접 트리거됩니다.", - "Submit to workflow": "워크플로우에 제출", "Add workflow": "워크플로우 추가", "Select workflow": "워크플로우 선택", "Trigger data context": "트리거 데이터 컨텍스트", @@ -244,4 +243,4 @@ "SQL action": "SQL 작업", "Execute a SQL statement in database": "데이터베이스에서 SQL 문을 실행합니다", "Usage of SQL query result is not supported yet.": "아직 SQL 쿼리 결과의 사용은 지원되지 않습니다." -} \ No newline at end of file +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json b/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json index 7e20dacfe..5729cc3c5 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json +++ b/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json @@ -281,7 +281,6 @@ "Status": "状态", "String operation": "字符串", "Submission may be withdrawn, please try refresh the list.": "提交可能已被撤回,请尝试刷新列表。", - "Submit to workflow": "提交至工作流", "Submitted": "提交", "Succeed and continue": "通过并继续", "Succeeded": "成功", diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts index 64d065b1b..4df843dec 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts @@ -8,9 +8,7 @@ import LRUCache from 'lru-cache'; import initActions from './actions'; import { EXECUTION_STATUS } from './constants'; -import { PluginActionTrigger } from './features/action-trigger/Plugin'; import { PluginAggregate } from './features/aggregate/Plugin'; -import PluginWorkflowAPIRegularServer from './features/api-regular/plugin'; import PluginWorkflowApproval from './features/approval'; import PluginWorkflowDataMappingServer from './features/data-mapping/plugin'; import { PluginDelay } from './features/delay/Plugin'; @@ -75,7 +73,6 @@ export default class PluginWorkflowServer extends Plugin { this.addFeature(PluginDynamicCalculation); this.addFeature(PluginDelay); this.addFeature(PluginAggregate); - this.addFeature(PluginActionTrigger); this.addFeature(PluginWorkflowJSONParseServer); this.addFeature(PluginWorkflowJSParseServer); this.addFeature(PluginWorkflowDataMappingServer); diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/ActionTrigger.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/ActionTrigger.ts deleted file mode 100644 index 246014c4c..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/ActionTrigger.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { Context as ActionContext, Next } from '@tachybase/actions'; -import { parseCollectionName } from '@tachybase/data-source-manager'; -import { Model, modelAssociationByKey } from '@tachybase/database'; -import Application, { DefaultContext } from '@tachybase/server'; - -import { get } from 'lodash'; -import { BelongsTo, HasOne } from 'sequelize'; - -import WorkflowPlugin, { toJSON, Trigger, WorkflowModel } from '../..'; - -interface Context extends ActionContext, DefaultContext {} - -export default class extends Trigger { - constructor(workflow: WorkflowPlugin) { - super(workflow); - - workflow.app.use(this.middleware, { after: 'dataSource' }); - } - - async triggerAction(context: Context, next: Next) { - const { triggerWorkflows } = context.action.params; - - if (!triggerWorkflows) { - return context.throw(400); - } - - context.status = 202; - await next(); - - this.trigger(context); - } - - middleware = async (context: Context, next: Next) => { - const { - resourceName, - actionName, - params: { triggerWorkflows }, - } = context.action; - - if (resourceName === 'workflows' && actionName === 'trigger') { - return this.triggerAction(context, next); - } - - await next(); - - if (!triggerWorkflows) { - return; - } - - if (!['create', 'update'].includes(actionName)) { - return; - } - - return this.trigger(context); - }; - - private async trigger(context: Context) { - const { triggerWorkflows = '', values } = context.action.params; - const dataSourceHeader = context.get('x-data-source') || 'main'; - - const { currentUser, currentRole } = context.state; - const { model: UserModel } = this.workflow.db.getCollection('users'); - const userInfo = { - user: UserModel.build(currentUser).desensitize(), - roleName: currentRole, - }; - - const triggers = triggerWorkflows.split(',').map((trigger) => trigger.split('!')); - const workflowRepo = this.workflow.db.getRepository('workflows'); - const workflows = ( - await workflowRepo.find({ - filter: { - key: triggers.map((trigger) => trigger[0]), - current: true, - type: 'action', - enabled: true, - }, - }) - ).filter((workflow) => Boolean(workflow.config.collection)); - const syncGroup = []; - const asyncGroup = []; - for (const workflow of workflows) { - const { collection, appends = [] } = workflow.config; - const [dataSourceName, collectionName] = parseCollectionName(collection); - const trigger = triggers.find((trigger) => trigger[0] == workflow.key); - const event = [workflow]; - if (context.action.resourceName !== 'workflows') { - if (!context.body) { - continue; - } - if (dataSourceName !== dataSourceHeader) { - continue; - } - const { body: data } = context; - for (const row of Array.isArray(data) ? data : [data]) { - let payload = row; - if (trigger[1]) { - const paths = trigger[1].split('.'); - for (const field of paths) { - if (payload.get(field)) { - payload = payload.get(field); - } else { - const association = modelAssociationByKey(payload, field); - payload = await payload[association.accessors.get](); - } - } - } - const model = payload.constructor; - if (payload instanceof Model) { - if (collectionName !== model.collection.name) { - continue; - } - if (appends.length) { - payload = await model.collection.repository.findOne({ - filterByTk: payload.get(model.primaryKeyAttribute), - appends, - }); - } - } - // this.workflow.trigger(workflow, { data: toJSON(payload), ...userInfo }); - event.push({ data: toJSON(payload), ...userInfo }); - } - } else { - const { model, repository } = (context.app).dataSourceManager.dataSources - .get(dataSourceName) - .collectionManager.getCollection(collectionName); - let data = trigger[1] ? get(values, trigger[1]) : values; - const pk = get(data, model.primaryKeyAttribute); - if (appends.length && pk != null) { - data = await repository.findOne({ - filterByTk: pk, - appends, - }); - } - // this.workflow.trigger(workflow, { - // data, - // ...userInfo, - // }); - event.push({ data, ...userInfo }); - } - (workflow.sync ? syncGroup : asyncGroup).push(event); - } - - for (const event of syncGroup) { - await this.workflow.trigger(event[0], event[1], { httpContext: context }); - } - - for (const event of asyncGroup) { - this.workflow.trigger(event[0], event[1]); - } - } - - on(workflow: WorkflowModel) {} - - off(workflow: WorkflowModel) {} -} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/Plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/Plugin.ts deleted file mode 100644 index 039a9e12d..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/Plugin.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Plugin } from '@tachybase/server'; - -import WorkflowPlugin from '../..'; -import ActionTrigger from './ActionTrigger'; - -export class PluginActionTrigger extends Plugin { - async load() { - const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin; - workflowPlugin.registerTrigger('action', new ActionTrigger(workflowPlugin)); - } -} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/__tests__/trigger.test.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/__tests__/trigger.test.ts deleted file mode 100644 index ac62788a3..000000000 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/features/action-trigger/__tests__/trigger.test.ts +++ /dev/null @@ -1,619 +0,0 @@ -import Database from '@tachybase/database'; -import { EXECUTION_STATUS } from '@tachybase/plugin-workflow'; -import { getApp, sleep } from '@tachybase/plugin-workflow-test'; -import { MockServer } from '@tachybase/test'; - -import Plugin from '..'; - -describe('workflow > action-trigger', () => { - let app: MockServer; - let db: Database; - let agent; - let PostRepo; - let CommentRepo; - let WorkflowModel; - let UserRepo; - let users; - let userAgents; - - beforeEach(async () => { - app = await getApp({ - plugins: ['users', 'auth', Plugin], - }); - await app.pm.get('auth').install(); - agent = app.agent(); - db = app.db; - WorkflowModel = db.getCollection('workflows').model; - PostRepo = db.getCollection('posts').repository; - CommentRepo = db.getCollection('comments').repository; - UserRepo = db.getCollection('users').repository; - - users = await UserRepo.create({ - values: [ - { id: 2, nickname: 'a', roles: [{ name: 'root' }] }, - { id: 3, nickname: 'b' }, - ], - }); - - userAgents = users.map((user) => app.agent().login(user)); - }); - - afterEach(() => app.destroy()); - - describe('create', () => { - it('enabled / disabled', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(1); - expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1[0].context.data).toMatchObject({ title: 't1' }); - - await workflow.update({ - enabled: false, - }); - - const res2 = await userAgents[0].resource('posts').create({ - values: { title: 't2' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res2.status).toBe(200); - - await sleep(500); - - const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] }); - expect(e2.length).toBe(1); - - await workflow.update({ - enabled: true, - }); - - const res3 = await userAgents[0].resource('posts').create({ - values: { title: 't3' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res2.status).toBe(200); - - await sleep(500); - - const e3 = await workflow.getExecutions({ order: [['id', 'ASC']] }); - expect(e3.length).toBe(2); - expect(e3[1].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e3[1].context.data).toMatchObject({ title: 't3' }); - }); - - it('only trigger if params provided matching collection config', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(1); - expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1[0].context.data).toMatchObject({ title: 't1' }); - - await workflow.update({ - config: { - collection: 'comments', - }, - }); - - const res2 = await userAgents[0].resource('posts').create({ - values: { title: 't2' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res2.status).toBe(200); - - await sleep(500); - - const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] }); - expect(e2.length).toBe(1); - // expect(e2[1].status).toBe(EXECUTION_STATUS.RESOLVED); - // expect(e2[1].context.data).toMatchObject({ title: 't2' }); - }); - - it('system fields could be accessed', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(1); - expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1[0].context.data).toHaveProperty('createdAt'); - }); - - it('appends', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - appends: ['createdBy'], - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(1); - expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1[0].context.data).toHaveProperty('createdBy'); - }); - - it('user submitted form', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.user).toBeDefined(); - expect(e1.context.user.id).toBe(users[0].id); - }); - }); - - describe('update', () => { - it('trigger after updated', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - appends: ['createdBy'], - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(0); - - const res2 = await userAgents[0].resource('posts').update({ - filterByTk: res1.body.data.id, - values: { title: 't2' }, - triggerWorkflows: `${workflow.key}`, - }); - - await sleep(500); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e2.context.data).toHaveProperty('title', 't2'); - expect(e2.context.data).toHaveProperty('createdBy'); - }); - }); - - describe.skip('destroy', () => { - it('trigger after destroyed', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const p1 = await PostRepo.create({ - values: { title: 't1' }, - }); - const p2 = await PostRepo.create({ - values: { title: 't2' }, - }); - const res1 = await userAgents[0].resource('posts').destroy({ - filterByTk: p1.id, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1 = await workflow.getExecutions(); - expect(e1.length).toBe(0); - - const res2 = await userAgents[0].resource('posts').destroy({ - filterByTk: p2.id, - triggerWorkflows: `${workflow.key}`, - }); - - await sleep(500); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e2.context.data).toBe(1); - }); - }); - - describe('directly trigger', () => { - it('no collection configured should not be triggered', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const e1s = await workflow.getExecutions(); - expect(e1s.length).toBe(0); - }); - - it('trigger on form data', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - appends: ['createdBy'], - }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 't1' }); - expect(e1.context.data.createdBy).toBeUndefined(); - }); - - it('trigger on record data', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - appends: ['createdBy'], - }, - }); - - const post = await PostRepo.create({ - values: { title: 't1', createdBy: users[0].id }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: post.toJSON(), - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 't1' }); - expect(e1.context.data).toHaveProperty('createdBy'); - expect(e1.context.data.createdBy.id).toBe(users[0].id); - }); - - it('multi trigger', async () => { - const w1 = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const w2 = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't1' }, - triggerWorkflows: `${w1.key},${w2.key}`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await w1.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 't1' }); - - const [e2] = await w2.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e2.context.data).toMatchObject({ title: 't1' }); - }); - - it('user submitted form', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - appends: ['createdBy'], - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.user).toBeDefined(); - expect(e1.context.user.id).toBe(users[0].id); - }); - }); - - describe('context data path', () => { - it('level: 1', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't1', category: { title: 'c1' } }, - triggerWorkflows: `${workflow.key}!category`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 'c1' }); - }); - - it('level: 2', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { content: 'comment1', post: { category: { title: 'c1' } } }, - triggerWorkflows: `${workflow.key}!post.category`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 'c1' }); - }); - }); - - describe('workflow key', () => { - it('revision', async () => { - const w1 = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't1' }, - triggerWorkflows: `${w1.key}`, - }); - expect(res1.status).toBe(202); - - await sleep(500); - - const [e1] = await w1.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e1.context.data).toMatchObject({ title: 't1' }); - - const res2 = await userAgents[0].resource('workflows').revision({ - filterByTk: w1.id, - filter: { - key: w1.key, - }, - }); - const w2 = await WorkflowModel.findByPk(res2.body.data.id); - await w2.update({ - enabled: true, - }); - - const res3 = await userAgents[0].resource('workflows').trigger({ - values: { title: 't2' }, - triggerWorkflows: `${w1.key}`, - }); - expect(res3.status).toBe(202); - - await sleep(500); - - const e2 = await w1.getExecutions(); - expect(e2.length).toBe(1); - const e3 = await w2.getExecutions(); - expect(e3.length).toBe(1); - expect(e3[0].status).toBe(EXECUTION_STATUS.RESOLVED); - expect(e3[0].context.data).toMatchObject({ title: 't2' }); - }); - }); - - describe('sync', () => { - it('sync form trigger', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - sync: true, - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - const executions = await workflow.getExecutions(); - expect(executions.length).toBe(1); - expect(executions[0].status).toBe(EXECUTION_STATUS.RESOLVED); - }); - - it('sync and async will all be triggered in one action', async () => { - const w1 = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'posts', - }, - }); - - const w2 = await WorkflowModel.create({ - enabled: true, - type: 'action', - sync: true, - config: { - collection: 'posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: [w1.key, w2.key].join(), - }); - expect(res1.status).toBe(200); - - const e1s = await w1.getExecutions(); - expect(e1s.length).toBe(0); - - const e2s = await w2.getExecutions(); - expect(e2s.length).toBe(1); - expect(e2s[0].status).toBe(EXECUTION_STATUS.RESOLVED); - - await sleep(500); - - const e3s = await w1.getExecutions(); - expect(e3s.length).toBe(1); - expect(e3s[0].status).toBe(EXECUTION_STATUS.RESOLVED); - }); - }); - - describe('multiple data source', () => { - it('trigger on different data source', async () => { - const workflow = await WorkflowModel.create({ - enabled: true, - type: 'action', - config: { - collection: 'another:posts', - }, - }); - - const res1 = await userAgents[0].resource('posts').create({ - values: { title: 't1' }, - triggerWorkflows: `${workflow.key}`, - }); - expect(res1.status).toBe(200); - - await sleep(500); - - const e1s = await workflow.getExecutions(); - expect(e1s.length).toBe(0); - - // const res2 = await userAgents[0] - // .set('x-data-source', 'another') - // .resource('posts') - // .create({ - // values: { title: 't2' }, - // triggerWorkflows: `${workflow.key}`, - // }); - const res2 = await agent - .login(users[0]) - .set('x-data-source', 'another') - .post('/api/posts:create') - .query({ triggerWorkflows: `${workflow.key}` }) - .send({ title: 't2' }); - - expect(res2.status).toBe(200); - - await sleep(500); - - const e2s = await workflow.getExecutions(); - expect(e2s.length).toBe(1); - expect(e2s[0].status).toBe(EXECUTION_STATUS.RESOLVED); - }); - }); -});