feat(workflow): 移除工作流操作类型的触发事件 (#1561)

Reviewed-on: daoyoucloud/tachybase#1561
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: bai.zixv <bai.zixv@foxmail.com>
Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
bai.zixv 2024-09-24 15:14:28 +08:00 committed by sealday
parent 569dbf2423
commit 04cd8135ca
11 changed files with 1 additions and 1875 deletions

View File

@ -4,7 +4,6 @@ import { Registry } from '@tachybase/utils/client';
import { WorkflowLink } from './components/WorkflowLink'; import { WorkflowLink } from './components/WorkflowLink';
import { ExecutionPage } from './ExecutionPage'; import { ExecutionPage } from './ExecutionPage';
import { PluginActionTrigger } from './features/action-trigger';
import { PluginAggregate } from './features/aggregate'; import { PluginAggregate } from './features/aggregate';
import PluginApproval from './features/approval'; import PluginApproval from './features/approval';
import PluginWorkflowDataMappingClient from './features/data-mapping'; import PluginWorkflowDataMappingClient from './features/data-mapping';
@ -86,7 +85,6 @@ export class PluginWorkflow extends Plugin {
await this.pm.add(PluginDaynamicCalculation); await this.pm.add(PluginDaynamicCalculation);
await this.pm.add(PluginDelay); await this.pm.add(PluginDelay);
await this.pm.add(PluginAggregate); await this.pm.add(PluginAggregate);
await this.pm.add(PluginActionTrigger);
await this.pm.add(PluginWorkflowJsonParseClient); await this.pm.add(PluginWorkflowJsonParseClient);
await this.pm.add(PluginWorkflowJSParseClient); await this.pm.add(PluginWorkflowJSParseClient);
await this.pm.add(PluginWorkflowDataMappingClient); await this.pm.add(PluginWorkflowDataMappingClient);

View File

@ -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',
};
}
}

View File

@ -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', () => {});

View File

@ -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', () => {});

View File

@ -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);
}
}

View File

@ -28,7 +28,6 @@
"Workflow will be triggered after submitting succeeded.": "제출 성공 후 워크플로우가 트리거됩니다.", "Workflow will be triggered after submitting succeeded.": "제출 성공 후 워크플로우가 트리거됩니다.",
"Workflow will be triggered after saving succeeded.": "저장 성공 후 워크플로우가 트리거됩니다.", "Workflow will be triggered after saving succeeded.": "저장 성공 후 워크플로우가 트리거됩니다.",
"Workflow will be triggered directly once the button is clicked, without data saving.": "버튼을 클릭하면 데이터 저장 없이 워크플로우가 직접 트리거됩니다.", "Workflow will be triggered directly once the button is clicked, without data saving.": "버튼을 클릭하면 데이터 저장 없이 워크플로우가 직접 트리거됩니다.",
"Submit to workflow": "워크플로우에 제출",
"Add workflow": "워크플로우 추가", "Add workflow": "워크플로우 추가",
"Select workflow": "워크플로우 선택", "Select workflow": "워크플로우 선택",
"Trigger data context": "트리거 데이터 컨텍스트", "Trigger data context": "트리거 데이터 컨텍스트",
@ -244,4 +243,4 @@
"SQL action": "SQL 작업", "SQL action": "SQL 작업",
"Execute a SQL statement in database": "데이터베이스에서 SQL 문을 실행합니다", "Execute a SQL statement in database": "데이터베이스에서 SQL 문을 실행합니다",
"Usage of SQL query result is not supported yet.": "아직 SQL 쿼리 결과의 사용은 지원되지 않습니다." "Usage of SQL query result is not supported yet.": "아직 SQL 쿼리 결과의 사용은 지원되지 않습니다."
} }

View File

@ -281,7 +281,6 @@
"Status": "状态", "Status": "状态",
"String operation": "字符串", "String operation": "字符串",
"Submission may be withdrawn, please try refresh the list.": "提交可能已被撤回,请尝试刷新列表。", "Submission may be withdrawn, please try refresh the list.": "提交可能已被撤回,请尝试刷新列表。",
"Submit to workflow": "提交至工作流",
"Submitted": "提交", "Submitted": "提交",
"Succeed and continue": "通过并继续", "Succeed and continue": "通过并继续",
"Succeeded": "成功", "Succeeded": "成功",

View File

@ -8,9 +8,7 @@ import LRUCache from 'lru-cache';
import initActions from './actions'; import initActions from './actions';
import { EXECUTION_STATUS } from './constants'; import { EXECUTION_STATUS } from './constants';
import { PluginActionTrigger } from './features/action-trigger/Plugin';
import { PluginAggregate } from './features/aggregate/Plugin'; import { PluginAggregate } from './features/aggregate/Plugin';
import PluginWorkflowAPIRegularServer from './features/api-regular/plugin';
import PluginWorkflowApproval from './features/approval'; import PluginWorkflowApproval from './features/approval';
import PluginWorkflowDataMappingServer from './features/data-mapping/plugin'; import PluginWorkflowDataMappingServer from './features/data-mapping/plugin';
import { PluginDelay } from './features/delay/Plugin'; import { PluginDelay } from './features/delay/Plugin';
@ -75,7 +73,6 @@ export default class PluginWorkflowServer extends Plugin {
this.addFeature(PluginDynamicCalculation); this.addFeature(PluginDynamicCalculation);
this.addFeature(PluginDelay); this.addFeature(PluginDelay);
this.addFeature(PluginAggregate); this.addFeature(PluginAggregate);
this.addFeature(PluginActionTrigger);
this.addFeature(PluginWorkflowJSONParseServer); this.addFeature(PluginWorkflowJSONParseServer);
this.addFeature(PluginWorkflowJSParseServer); this.addFeature(PluginWorkflowJSParseServer);
this.addFeature(PluginWorkflowDataMappingServer); this.addFeature(PluginWorkflowDataMappingServer);

View File

@ -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 = <HasOne | BelongsTo>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 } = (<Application>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) {}
}

View File

@ -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));
}
}

View File

@ -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);
});
});
});