mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 06:59:26 +08:00
refactor(plugin-workflow): support any context in processor as options (#3473)
* refactor(plugin-workflow): support any context in processor as options * fix(plugin-workflow): fix collection trigger * fix(plugin-workflow): fix collection trigger and instructions * fix(plugin-workflow): fix bind workflow configuration on button * fix(plugin-workflow): fix e2e test cases
This commit is contained in:
parent
cd09649e1c
commit
4d4a70b2f4
@ -3,7 +3,7 @@ import { Field, onFieldValueChange } from '@formily/core';
|
|||||||
import { ISchema, connect, mapProps, useField, useFieldSchema, useForm, useFormEffects } from '@formily/react';
|
import { ISchema, connect, mapProps, useField, useFieldSchema, useForm, useFormEffects } from '@formily/react';
|
||||||
import { isValid, uid } from '@formily/shared';
|
import { isValid, uid } from '@formily/shared';
|
||||||
import { Alert, Tree as AntdTree, ModalProps } from 'antd';
|
import { Alert, Tree as AntdTree, ModalProps } from 'antd';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RemoteSelect, useCompile, useDesignable } from '../..';
|
import { RemoteSelect, useCompile, useDesignable } from '../..';
|
||||||
import { useApp } from '../../../application';
|
import { useApp } from '../../../application';
|
||||||
@ -28,6 +28,7 @@ import {
|
|||||||
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
import { requestSettingsSchema } from './utils';
|
import { requestSettingsSchema } from './utils';
|
||||||
|
import { useFormBlockContext } from '../../../block-provider';
|
||||||
|
|
||||||
const Tree = connect(
|
const Tree = connect(
|
||||||
AntdTree,
|
AntdTree,
|
||||||
@ -447,13 +448,26 @@ function RemoveButton(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WorkflowSelect({ types, ...props }) {
|
function WorkflowSelect({ actionType, ...props }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const index = ArrayTable.useIndex();
|
const index = ArrayTable.useIndex();
|
||||||
const { setValuesIn } = useForm();
|
const { setValuesIn } = useForm();
|
||||||
const baseCollection = useCollection();
|
const baseCollection = useCollection();
|
||||||
const { getCollection } = useCollectionManager();
|
const { getCollection } = useCollectionManager();
|
||||||
const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
|
const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
|
||||||
|
|
||||||
|
const workflowPlugin = usePlugin('workflow') as any;
|
||||||
|
const workflowTypes = useMemo(
|
||||||
|
() =>
|
||||||
|
workflowPlugin
|
||||||
|
.getTriggersOptions()
|
||||||
|
.filter((item) => {
|
||||||
|
return typeof item.options.isActionTriggerable === 'function' || item.options.isActionTriggerable === true;
|
||||||
|
})
|
||||||
|
.map((item) => item.value),
|
||||||
|
[workflowPlugin],
|
||||||
|
);
|
||||||
|
|
||||||
useFormEffects(() => {
|
useFormEffects(() => {
|
||||||
onFieldValueChange(`group[${index}].context`, (field) => {
|
onFieldValueChange(`group[${index}].context`, (field) => {
|
||||||
let collection: CollectionOptions = baseCollection;
|
let collection: CollectionOptions = baseCollection;
|
||||||
@ -472,6 +486,20 @@ function WorkflowSelect({ types, ...props }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const optionFilter = useCallback(
|
||||||
|
({ type, config }) => {
|
||||||
|
const trigger = workflowPlugin.triggers.get(type);
|
||||||
|
if (trigger.isActionTriggerable === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof trigger.isActionTriggerable === 'function') {
|
||||||
|
return trigger.isActionTriggerable(config, { action: actionType });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[workflowPlugin, actionType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RemoteSelect
|
<RemoteSelect
|
||||||
manual={false}
|
manual={false}
|
||||||
@ -485,12 +513,13 @@ function WorkflowSelect({ types, ...props }) {
|
|||||||
action: 'list',
|
action: 'list',
|
||||||
params: {
|
params: {
|
||||||
filter: {
|
filter: {
|
||||||
type: types,
|
type: workflowTypes,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
'config.collection': workflowCollection,
|
'config.collection': workflowCollection,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
optionFilter={optionFilter}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -501,23 +530,26 @@ function WorkflowConfig() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { name: collection } = useCollection();
|
const { name: collection } = useCollection();
|
||||||
const workflowPlugin = usePlugin('workflow') as any;
|
// TODO(refactor): should refactor for getting certain action type, better from 'x-action'.
|
||||||
const workflowTypes = workflowPlugin.getTriggersOptions().filter((item) => {
|
const formBlock = useFormBlockContext();
|
||||||
return typeof item.options.useActionTriggerable === 'function'
|
const actionType = formBlock?.type || fieldSchema['x-action'];
|
||||||
? item.options.useActionTriggerable()
|
|
||||||
: Boolean(item.options.useActionTriggerable);
|
|
||||||
});
|
|
||||||
const description = {
|
const description = {
|
||||||
submit: t('Workflow will be triggered after submitting succeeded.', { ns: 'workflow' }),
|
submit: t('Workflow will be triggered before or after submitting succeeded based on workflow type.', {
|
||||||
'customize:save': t('Workflow will be triggered after saving succeeded.', { ns: 'workflow' }),
|
ns: 'workflow',
|
||||||
|
}),
|
||||||
|
'customize:save': t('Workflow will be triggered before or after saving succeeded based on workflow type.', {
|
||||||
|
ns: 'workflow',
|
||||||
|
}),
|
||||||
'customize:triggerWorkflows': t(
|
'customize:triggerWorkflows': t(
|
||||||
'Workflow will be triggered directly once the button clicked, without data saving.',
|
'Workflow will be triggered directly once the button clicked, without data saving.',
|
||||||
{ ns: 'workflow' },
|
{ ns: 'workflow' },
|
||||||
),
|
),
|
||||||
|
destroy: t('Workflow will be triggered before deleting succeeded.', { ns: 'workflow' }),
|
||||||
}[fieldSchema?.['x-action']];
|
}[fieldSchema?.['x-action']];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsModalItem
|
<SchemaSettingsActionModalItem
|
||||||
title={t('Bind workflows', { ns: 'workflow' })}
|
title={t('Bind workflows', { ns: 'workflow' })}
|
||||||
scope={{
|
scope={{
|
||||||
fieldFilter(field) {
|
fieldFilter(field) {
|
||||||
@ -573,6 +605,7 @@ function WorkflowConfig() {
|
|||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
allowClear: false,
|
allowClear: false,
|
||||||
|
loadData: actionType === 'destroy' ? null : undefined,
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
@ -590,7 +623,8 @@ function WorkflowConfig() {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'WorkflowSelect',
|
'x-component': 'WorkflowSelect',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
types: workflowTypes.map((item) => item.value),
|
placeholder: t('Select workflow', { ns: 'workflow' }),
|
||||||
|
actionType,
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CloseCircleFilled } from '@ant-design/icons';
|
import { CloseCircleFilled } from '@ant-design/icons';
|
||||||
import { Tag, TreeSelect } from 'antd';
|
import { Tag, TreeSelect } from 'antd';
|
||||||
import type { DefaultOptionType } from 'rc-tree-select/es/TreeSelect';
|
import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CollectionFieldOptions, useCollectionManager, useCompile } from '../../..';
|
import { CollectionFieldOptions, useCollectionManager, useCompile } from '../../..';
|
||||||
@ -72,7 +72,7 @@ function getCollectionFieldOptions(this: CallScope, collection, parentNode?): Tr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
export const AppendsTreeSelect: React.FC<TreeSelectProps & AppendsTreeSelectProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
value: propsValue,
|
value: propsValue,
|
||||||
@ -81,6 +81,7 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
useCollection = usePropsCollection,
|
useCollection = usePropsCollection,
|
||||||
filter = trueFilter,
|
filter = trueFilter,
|
||||||
rootOption,
|
rootOption,
|
||||||
|
loadData: propsLoadData,
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
@ -98,6 +99,9 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
|
|
||||||
const loadData = useCallback(
|
const loadData = useCallback(
|
||||||
async (option) => {
|
async (option) => {
|
||||||
|
if (propsLoadData !== null) {
|
||||||
|
return propsLoadData(option);
|
||||||
|
}
|
||||||
if (!option.isLeaf && option.loadChildren) {
|
if (!option.isLeaf && option.loadChildren) {
|
||||||
const children = option.loadChildren(option);
|
const children = option.loadChildren(option);
|
||||||
setOptionsMap((prev) => {
|
setOptionsMap((prev) => {
|
||||||
@ -105,7 +109,7 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setOptionsMap],
|
[propsLoadData],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -119,17 +123,16 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
const treeData = getCollectionFieldOptions.call(
|
const treeData =
|
||||||
{ compile, getCollectionFields, filter },
|
propsLoadData === null
|
||||||
baseCollection,
|
? []
|
||||||
parentNode,
|
: getCollectionFieldOptions.call({ compile, getCollectionFields, filter }, baseCollection, parentNode);
|
||||||
);
|
|
||||||
const map = treeData.reduce((result, item) => Object.assign(result, { [item.value]: item }), {});
|
const map = treeData.reduce((result, item) => Object.assign(result, { [item.value]: item }), {});
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
map[parentNode.value] = parentNode;
|
map[parentNode.value] = parentNode;
|
||||||
}
|
}
|
||||||
setOptionsMap(map);
|
setOptionsMap(map);
|
||||||
}, [collection, baseCollection, rootOption, filter]);
|
}, [collection, baseCollection, rootOption, filter, propsLoadData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const arr = (props.multiple ? propsValue : propsValue ? [propsValue] : []) as string[];
|
const arr = (props.multiple ? propsValue : propsValue ? [propsValue] : []) as string[];
|
||||||
@ -217,8 +220,8 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
const valueKeys: string[] = props.multiple
|
const valueKeys: string[] = props.multiple
|
||||||
? (propsValue as string[])
|
? (propsValue as string[])
|
||||||
: propsValue != null
|
: propsValue != null
|
||||||
? [propsValue as string]
|
? [propsValue as string]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
@ -22,6 +22,7 @@ export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
|
|||||||
targetField?: any;
|
targetField?: any;
|
||||||
service: ResourceActionOptions<P>;
|
service: ResourceActionOptions<P>;
|
||||||
CustomDropdownRender?: (v: any) => any;
|
CustomDropdownRender?: (v: any) => any;
|
||||||
|
optionFilter?: (option: any) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalRemoteSelect = connect(
|
const InternalRemoteSelect = connect(
|
||||||
@ -37,6 +38,7 @@ const InternalRemoteSelect = connect(
|
|||||||
mapOptions,
|
mapOptions,
|
||||||
targetField: _targetField,
|
targetField: _targetField,
|
||||||
CustomDropdownRender,
|
CustomDropdownRender,
|
||||||
|
optionFilter,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@ -192,8 +194,9 @@ const InternalRemoteSelect = connect(
|
|||||||
}
|
}
|
||||||
const valueOptions =
|
const valueOptions =
|
||||||
(v != null && (Array.isArray(v) ? v : [{ ...v, [fieldNames.value]: v[fieldNames.value] || v }])) || [];
|
(v != null && (Array.isArray(v) ? v : [{ ...v, [fieldNames.value]: v[fieldNames.value] || v }])) || [];
|
||||||
return uniqBy(data?.data?.concat(valueOptions ?? []), fieldNames.value);
|
const filtered = typeof optionFilter === 'function' ? data.data.filter(optionFilter) : data.data;
|
||||||
}, [value, defaultValue, data?.data, fieldNames.value]);
|
return uniqBy(filtered.concat(valueOptions ?? []), fieldNames.value);
|
||||||
|
}, [value, defaultValue, data?.data, fieldNames.value, optionFilter]);
|
||||||
const onDropdownVisibleChange = (visible) => {
|
const onDropdownVisibleChange = (visible) => {
|
||||||
setOpen(visible);
|
setOpen(visible);
|
||||||
searchData.current = null;
|
searchData.current = null;
|
||||||
|
@ -22,6 +22,9 @@ export const BulkDestroyActionInitializer = (props) => {
|
|||||||
},
|
},
|
||||||
useProps: '{{ useBulkDestroyActionProps }}',
|
useProps: '{{ useBulkDestroyActionProps }}',
|
||||||
},
|
},
|
||||||
|
'x-action-settings': {
|
||||||
|
triggerWorkflows: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (collection) {
|
if (collection) {
|
||||||
schema['x-acl-action'] = `${collection.name}:destroy`;
|
schema['x-acl-action'] = `${collection.name}:destroy`;
|
||||||
|
@ -16,6 +16,9 @@ export const DestroyActionInitializer = (props) => {
|
|||||||
},
|
},
|
||||||
useProps: '{{ useDestroyActionProps }}',
|
useProps: '{{ useDestroyActionProps }}',
|
||||||
},
|
},
|
||||||
|
'x-action-settings': {
|
||||||
|
triggerWorkflows: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return <ActionInitializer {...props} schema={schema} />;
|
return <ActionInitializer {...props} schema={schema} />;
|
||||||
};
|
};
|
||||||
|
@ -59,7 +59,7 @@ export default class extends Trigger {
|
|||||||
scope = {
|
scope = {
|
||||||
useCollectionDataSource,
|
useCollectionDataSource,
|
||||||
};
|
};
|
||||||
useActionTriggerable = true;
|
isActionTriggerable = true;
|
||||||
useVariables(config, options) {
|
useVariables(config, options) {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
@ -93,7 +93,7 @@ test.describe('Configuration page to configure the Trigger node', () => {
|
|||||||
await page.getByRole('button', { name: 'plus Add workflow' }).click();
|
await page.getByRole('button', { name: 'plus Add workflow' }).click();
|
||||||
await page.getByRole('button', { name: 'Select workflow' }).click();
|
await page.getByRole('button', { name: 'Select workflow' }).click();
|
||||||
await page.getByRole('option', { name: workFlowName }).click();
|
await page.getByRole('option', { name: workFlowName }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'Submit', exact: true }).click();
|
||||||
|
|
||||||
// 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮
|
// 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮
|
||||||
const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||||
@ -187,7 +187,7 @@ test.describe('Configuration page to configure the Trigger node', () => {
|
|||||||
await page.getByRole('button', { name: 'plus Add workflow' }).click();
|
await page.getByRole('button', { name: 'plus Add workflow' }).click();
|
||||||
await page.getByRole('button', { name: 'Select workflow' }).click();
|
await page.getByRole('button', { name: 'Select workflow' }).click();
|
||||||
await page.getByRole('option', { name: workFlowName }).click();
|
await page.getByRole('option', { name: workFlowName }).click();
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'Submit', exact: true }).click();
|
||||||
|
|
||||||
// 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮
|
// 2、测试步骤:进入“数据区块”-“添加”按钮,填写表单,点击“确定”按钮
|
||||||
const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
const fieldData = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||||
|
@ -116,14 +116,7 @@ export default class extends Trigger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const event of syncGroup) {
|
for (const event of syncGroup) {
|
||||||
await this.workflow.trigger(event[0], event[1]);
|
await this.workflow.trigger(event[0], event[1], { httpContext: context });
|
||||||
// if (processor.execution.status < EXECUTION_STATUS.STARTED) {
|
|
||||||
// // error handling
|
|
||||||
// return context.throw(
|
|
||||||
// 500,
|
|
||||||
// 'Your data saved, but some workflow on your action failed, please contact the administrator.',
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const event of asyncGroup) {
|
for (const event of asyncGroup) {
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { css, useCompile } from '@nocobase/client';
|
||||||
|
import { Checkbox, Space, Tooltip } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface CheckboxGroupWithTooltipOption {
|
||||||
|
value: any;
|
||||||
|
label: string;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CheckboxGroupWithTooltip(props) {
|
||||||
|
const { options = [], direction, ...other } = props;
|
||||||
|
const compile = useCompile();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox.Group {...other}>
|
||||||
|
<Space direction={direction}>
|
||||||
|
{options.map((option) => (
|
||||||
|
<Checkbox key={option.value} value={option.value}>
|
||||||
|
<span
|
||||||
|
className={css`
|
||||||
|
& + .anticon {
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{compile(option.label)}
|
||||||
|
</span>
|
||||||
|
{option.tooltip && (
|
||||||
|
<Tooltip title={compile(option.tooltip)}>
|
||||||
|
<QuestionCircleOutlined style={{ color: '#666' }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Checkbox>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Checkbox.Group>
|
||||||
|
);
|
||||||
|
}
|
@ -2,4 +2,5 @@ export * from './CollectionBlockInitializer';
|
|||||||
export * from './FieldsSelect';
|
export * from './FieldsSelect';
|
||||||
export * from './FilterDynamicComponent';
|
export * from './FilterDynamicComponent';
|
||||||
export * from './RadioWithTooltip';
|
export * from './RadioWithTooltip';
|
||||||
|
export * from './CheckboxGroupWithTooltip';
|
||||||
export * from './ValueBlock';
|
export * from './ValueBlock';
|
||||||
|
@ -65,7 +65,7 @@ export abstract class Trigger {
|
|||||||
components?: { [key: string]: any };
|
components?: { [key: string]: any };
|
||||||
useInitializers?(config): SchemaInitializerItemType | null;
|
useInitializers?(config): SchemaInitializerItemType | null;
|
||||||
initializers?: any;
|
initializers?: any;
|
||||||
useActionTriggerable?: boolean | (() => boolean);
|
isActionTriggerable?: boolean | ((config: object, context?: object) => boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TriggerExecution() {
|
function TriggerExecution() {
|
||||||
|
@ -190,6 +190,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
'workflows.nodes:*',
|
'workflows.nodes:*',
|
||||||
'executions:list',
|
'executions:list',
|
||||||
'executions:get',
|
'executions:get',
|
||||||
|
'executions:cancel',
|
||||||
'flow_nodes:update',
|
'flow_nodes:update',
|
||||||
'flow_nodes:destroy',
|
'flow_nodes:destroy',
|
||||||
],
|
],
|
||||||
@ -239,8 +240,8 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.app.on('beforeStop', async () => {
|
this.app.on('beforeStop', async () => {
|
||||||
const collection = db.getCollection('workflows');
|
const repository = db.getRepository('workflows');
|
||||||
const workflows = await collection.repository.find({
|
const workflows = await repository.find({
|
||||||
filter: { enabled: true },
|
filter: { enabled: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -280,7 +281,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
public trigger(
|
public trigger(
|
||||||
workflow: WorkflowModel,
|
workflow: WorkflowModel,
|
||||||
context: object,
|
context: object,
|
||||||
options: { context?: any } & Transactionable = {},
|
options: { [key: string]: any } & Transactionable = {},
|
||||||
): void | Promise<Processor | null> {
|
): void | Promise<Processor | null> {
|
||||||
const logger = this.getLogger(workflow.id);
|
const logger = this.getLogger(workflow.id);
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
@ -298,7 +299,8 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
return this.triggerSync(workflow, context, options);
|
return this.triggerSync(workflow, context, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.events.push([workflow, context, { context: options.context }]);
|
const { transaction, ...rest } = options;
|
||||||
|
this.events.push([workflow, context, rest]);
|
||||||
this.eventsCount = this.events.length;
|
this.eventsCount = this.events.length;
|
||||||
|
|
||||||
logger.info(`new event triggered, now events: ${this.events.length}`);
|
logger.info(`new event triggered, now events: ${this.events.length}`);
|
||||||
@ -317,7 +319,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
private async triggerSync(
|
private async triggerSync(
|
||||||
workflow: WorkflowModel,
|
workflow: WorkflowModel,
|
||||||
context: object,
|
context: object,
|
||||||
options: { context?: any } & Transactionable = {},
|
options: { [key: string]: any } & Transactionable = {},
|
||||||
): Promise<Processor | null> {
|
): Promise<Processor | null> {
|
||||||
let execution;
|
let execution;
|
||||||
try {
|
try {
|
||||||
|
@ -9,6 +9,7 @@ import type { ExecutionModel, FlowNodeModel, JobModel } from './types';
|
|||||||
|
|
||||||
export interface ProcessorOptions extends Transactionable {
|
export interface ProcessorOptions extends Transactionable {
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Processor {
|
export default class Processor {
|
||||||
|
@ -10,6 +10,7 @@ export class CreateInstruction extends Instruction {
|
|||||||
|
|
||||||
const { repository, model } = (<typeof FlowNodeModel>node.constructor).database.getCollection(collection);
|
const { repository, model } = (<typeof FlowNodeModel>node.constructor).database.getCollection(collection);
|
||||||
const options = processor.getParsedValue(params, node.id);
|
const options = processor.getParsedValue(params, node.id);
|
||||||
|
|
||||||
const created = await repository.create({
|
const created = await repository.create({
|
||||||
...options,
|
...options,
|
||||||
context: {
|
context: {
|
||||||
|
@ -88,20 +88,13 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M
|
|||||||
if (workflow.sync) {
|
if (workflow.sync) {
|
||||||
await this.workflow.trigger(
|
await this.workflow.trigger(
|
||||||
workflow,
|
workflow,
|
||||||
{ data: json },
|
{ data: json, stack: context?.stack },
|
||||||
{
|
{
|
||||||
context,
|
|
||||||
transaction,
|
transaction,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.workflow.trigger(
|
this.workflow.trigger(workflow, { data: json, stack: context?.stack });
|
||||||
workflow,
|
|
||||||
{ data: json },
|
|
||||||
{
|
|
||||||
context,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,15 +148,11 @@ export default class CollectionTrigger extends Trigger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateEvent(
|
async validateEvent(workflow: WorkflowModel, context: any, options: Transactionable): Promise<boolean> {
|
||||||
workflow: WorkflowModel,
|
if (context.stack) {
|
||||||
context: any,
|
|
||||||
options: { context?: { stack?: number[] } } & Transactionable,
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (options.context?.stack) {
|
|
||||||
const existed = await workflow.countExecutions({
|
const existed = await workflow.countExecutions({
|
||||||
where: {
|
where: {
|
||||||
id: options.context.stack,
|
id: context.stack,
|
||||||
},
|
},
|
||||||
transaction: options.transaction,
|
transaction: options.transaction,
|
||||||
});
|
});
|
||||||
@ -172,13 +161,11 @@ export default class CollectionTrigger extends Trigger {
|
|||||||
this.workflow
|
this.workflow
|
||||||
.getLogger(workflow.id)
|
.getLogger(workflow.id)
|
||||||
.warn(
|
.warn(
|
||||||
`workflow ${workflow.id} has already been triggered in stack executions (${options.context.stack}), and newly triggering will be skipped.`,
|
`workflow ${workflow.id} has already been triggered in stack executions (${context.stack}), and newly triggering will be skipped.`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.stack = options.context.stack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user