diff --git a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx index daed13ad6..f8b300457 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx @@ -336,7 +336,7 @@ export function RemoveButton( ); } -function WorkflowSelect({ formAction, buttonAction, actionType, direct = false, ...props }) { +export function WorkflowSelect({ formAction, buttonAction, actionType, direct = false, ...props }) { const { t } = useTranslation(); const index = ArrayTable.useIndex(); const { setValuesIn } = useForm(); diff --git a/packages/plugins/@hera/plugin-core/src/client/settings-manager-components/WebhookManager.tsx b/packages/plugins/@hera/plugin-core/src/client/settings-manager-components/WebhookManager.tsx index fbe088790..9fb8bbbd2 100644 --- a/packages/plugins/@hera/plugin-core/src/client/settings-manager-components/WebhookManager.tsx +++ b/packages/plugins/@hera/plugin-core/src/client/settings-manager-components/WebhookManager.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { ExtendCollectionsProvider, SchemaComponent } from '@tachybase/client'; +import { ExtendCollectionsProvider, SchemaComponent, WorkflowSelect } from '@tachybase/client'; import { ISchema } from '@tachybase/schema'; import { tval } from '../locale'; const collection = { name: 'webhooks', + title: 'webhooks', fields: [ { type: 'string', @@ -34,6 +35,21 @@ const collection = { default: false, } as ISchema, }, + { + type: 'string', + name: 'workflowKey', + interface: 'select', + uiSchema: { + title: tval('name'), + type: 'string', + 'x-component': 'WorkflowSelect', + 'x-component-props': { + buttonAction: 'customize:triggerWorkflows', + label: 'title', + value: 'key', + }, + } as ISchema, + }, { type: 'string', name: 'type', @@ -239,17 +255,11 @@ const schema: ISchema = { type: 'void', 'x-component': 'Grid.Col', properties: { - workflow: { + workflowKey: { type: 'string', 'x-component': 'CollectionField', 'x-decorator': 'FormItem', - 'x-collection-field': 'webhooks.workflow', - 'x-component-props': { - fieldNames: { - value: 'id', - label: 'name', - }, - }, + 'x-collection-field': 'webhooks.workflowKey', }, }, }, @@ -373,17 +383,9 @@ const schema: ISchema = { 'x-decorator': 'TableV2.Column.Decorator', 'x-component': 'TableV2.Column', properties: { - workflow: { - 'x-collection-field': 'webhooks.workflow', + workflowKey: { + 'x-collection-field': 'webhooks.workflowKey', 'x-component': 'CollectionField', - 'x-component-props': { - fieldNames: { - value: 'id', - label: 'name', - }, - ellipsis: true, - size: 'small', - }, 'x-read-pretty': true, 'x-decorator': null, 'x-decorator-props': { @@ -575,17 +577,11 @@ const schema: ISchema = { type: 'void', 'x-component': 'Grid.Col', properties: { - workflow: { + workflowKey: { type: 'string', 'x-component': 'CollectionField', 'x-decorator': 'FormItem', - 'x-collection-field': 'webhooks.workflow', - 'x-component-props': { - fieldNames: { - value: 'id', - label: 'name', - }, - }, + 'x-collection-field': 'webhooks.workflowKey', }, }, }, @@ -681,7 +677,7 @@ const schema: ISchema = { export const WebhookManager = () => { return ( - + ); }; diff --git a/packages/plugins/@hera/plugin-core/src/server/actions/webhooks.ts b/packages/plugins/@hera/plugin-core/src/server/actions/webhooks.ts index 6143c698b..580522587 100644 --- a/packages/plugins/@hera/plugin-core/src/server/actions/webhooks.ts +++ b/packages/plugins/@hera/plugin-core/src/server/actions/webhooks.ts @@ -1,4 +1,5 @@ import { Context } from '@tachybase/actions'; +import { PluginWorkflow } from '@tachybase/plugin-workflow'; import { Action, Controller } from '@tachybase/utils'; @Controller('webhooks') @@ -19,18 +20,33 @@ export class WebhookController { if (!name) { throw new Error('not support'); } + const pluginWorkflow = ctx.app.getPlugin(PluginWorkflow) as PluginWorkflow; const repo = ctx.db.getRepository('webhooks'); const webhook = await repo.findOne(where); const webhookCtx = { + request: ctx.request, action: ctx.action, body: '', }; run(webhook.code, { ctx: webhookCtx, }); + if (webhook?.workflowKey) { + const wfRepo = ctx.db.getRepository('workflows'); + const wf = await wfRepo.findOne({ filter: { key: webhook.workflowKey, enabled: true } }); + const processor = await pluginWorkflow.trigger(wf, { data: webhookCtx.body }, { httpContext: ctx }); + if (!processor) { + return ctx.throw(500); + } + const { lastSavedJob } = processor; + ctx.withoutDataWrapping = true; + ctx.body = lastSavedJob.result; + return; + } if (webhookCtx.body) { ctx.withoutDataWrapping = true; ctx.body = webhookCtx.body; + return; } } } diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.schema.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.schema.tsx index 12ae9233d..7e0247c47 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.schema.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.schema.tsx @@ -10,7 +10,7 @@ export const APIRegularInitializer = () => { const schema = { type: 'void', - title: tval('Regular workflow'), + title: tval('Trigger workflow'), 'x-component': 'Action', 'x-use-component-props': 'usePropsAPIRegular', 'x-align': 'right', @@ -21,10 +21,11 @@ const schema = { }, 'x-toolbar': 'ActionSchemaToolbar', 'x-settings': 'actionSettings:APIRegular', - 'x-action': 'customize:APIRegular', + 'x-action': 'customize:triggerWorkflows', 'x-action-settings': { bindWorkflow: false, updateMode: 'selected', + triggerWorkflows: [], }, 'x-component-props': { icon: 'CarryOutOutlined', diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.setting.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.setting.tsx index b984f0d7c..209d9af1f 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.setting.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/APIRegular.setting.tsx @@ -5,8 +5,9 @@ import { useDesignable, useRequest, useSchemaToolbar, + WorkflowConfig, } from '@tachybase/client'; -import { useFieldSchema } from '@tachybase/schema'; +import { isValid, useFieldSchema } from '@tachybase/schema'; import { useTranslation } from 'react-i18next'; @@ -47,6 +48,14 @@ const schemaSettingsItems: SchemaSettingsItemType[] = [ }; }, }, + { + name: 'workflowConfig', + Component: WorkflowConfig, + useVisible() { + const fieldSchema = useFieldSchema(); + return isValid(fieldSchema?.['x-action-settings']?.triggerWorkflows); + }, + }, { name: 'Bind workflow', type: 'select', diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/hooks.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/hooks.tsx index 44f2f229a..541e39996 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/hooks.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/hooks.tsx @@ -74,9 +74,11 @@ export const usePropsAPIRegular = () => { async onClick() { const selectedRecordKeys = tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? []; + const { filter } = service.params?.[0] ?? {}; const { bindWorkflow = false, + triggerWorkflows = false, assignedValues: originalAssignedValues = {}, updateMode, } = actionSchema?.['x-action-settings'] ?? {}; @@ -90,7 +92,7 @@ export const usePropsAPIRegular = () => { actionField.data = field.data || {}; actionField.data.loading = true; - if (!bindWorkflow) { + if (!triggerWorkflows && !bindWorkflow) { return modal.info({ title: lang('Not bind workflow!'), }); @@ -122,11 +124,36 @@ export const usePropsAPIRegular = () => { title: lang('Confirm', { ns: 'client' }), content: lang('Trigger workflow?', { ns: 'client' }), async onOk() { - const params = { - filterByTk: bindWorkflow, - updateData: encodeURIComponent(JSON.stringify(updateData)), - }; - run(params); + if (bindWorkflow) { + const params = { + filterByTk: bindWorkflow, + updateData: encodeURIComponent(JSON.stringify(updateData)), + }; + run(params); + } else if (triggerWorkflows) { + if (isUpdateSelected) { + await resource.trigger({ + filterByTk: selectedRecordKeys, + triggerWorkflows: + triggerWorkflows && triggerWorkflows.length > 0 + ? triggerWorkflows + .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) + .join(',') + : [], + }); + } else { + await resource.trigger({ + filter, + triggerWorkflows: + triggerWorkflows && triggerWorkflows.length > 0 + ? triggerWorkflows + .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) + .join(',') + : [], + }); + } + } + // run(params); actionField.data.loading = false; }, async onCancel() { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/kit.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/kit.ts index b14ddc7a6..1ed77a257 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/kit.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/api-regular/usage/kit.ts @@ -19,7 +19,7 @@ export class KitAPIRegularUsage extends Plugin { ['table', 'details'].forEach((block) => { this.app.schemaInitializerManager.addItem(`${block}:configureActions`, 'customize.APIRegular', { - title: tval('Regular workflow'), + title: tval('Trigger workflow'), Component: 'APIRegularInitializer', name: 'apiRegular', useVisible() { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/OmniActionTrigger.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/OmniActionTrigger.tsx new file mode 100644 index 000000000..1769f7797 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/OmniActionTrigger.tsx @@ -0,0 +1,120 @@ +import { useCollectionDataSource, useCollectionManager_deprecated, useCompile } from '@tachybase/client'; +import { useForm } from '@tachybase/schema'; + +import { CollectionBlockInitializer } from '../../components'; +import { lang, tval } from '../../locale'; +import { Trigger } from '../../triggers'; +import { getCollectionFieldOptions, UseVariableOptions } from '../../variable'; + +export class OmniActionTrigger extends Trigger { + title = tval('Omni action event'); + description = tval( + `Omni Trigger is a versatile trigger. You can use it to trigger workflows in a table, trigger it from another workflow, or trigger it with a form button.`, + ); + fieldset = { + collection: { + type: 'string', + title: tval('Collection'), + 'x-decorator': 'FormItem', + 'x-component': 'DataSourceCollectionCascader', + 'x-reactions': [{ target: 'changed', effects: ['onFieldValueChange'], fulfill: { state: { value: [] } } }], + }, + isWebhook: { + type: 'boolean', + title: tval('is webhook'), + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + enum: [ + { label: tval('No'), value: false }, + { label: tval('Yes'), value: true }, + ], + required: true, + default: false, + 'x-reactions': [ + { + target: 'collection', + effects: ['onFieldValueChange'], + fulfill: { + state: { + value: 'webhooks', + }, + }, + }, + ], + }, + appends: { + type: 'array', + title: tval('Associations to use'), + description: tval( + '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.', + ), + 'x-decorator': 'FormItem', + 'x-component': 'AppendsTreeSelect', + 'x-component-props': { + multiple: true, + useCollection() { + const form = useForm(); + return form.values?.collection; + }, + }, + 'x-reactions': [{ dependencies: ['collection'], fulfill: { state: { visible: '{{!!$deps[0]}}' } } }], + }, + }; + scope = { useCollectionDataSource }; + components = {}; + isActionTriggerable = (config, context) => { + return context.buttonAction === 'customize:triggerWorkflows'; + }; + useVariables(config: Record, options?: UseVariableOptions) { + // 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(); + const fieldOptions = getCollectionFieldOptions({ + appends: ['user'], + ...options, + fields: [ + { + collectionName: 'users', + name: 'user', + type: 'hasOne', + target: 'users', + uiSchema: { title: lang('User acted') }, + }, + ], + compile, + getCollectionFields, + }); + return [ + ...getCollectionFieldOptions({ + appends: ['data', ...(config.appends?.map((append) => `data.${append}`) || [])], + fields: [ + { + collectionName: config.collection, + name: 'data', + type: 'hasOne', + target: config.collection, + uiSchema: { title: lang('Trigger data') }, + }, + ], + compile, + getCollectionFields, + }), + ...fieldOptions, + { label: lang('Role of user acted'), value: 'roleName' }, + ]; + } + useInitializers(config) { + return config.collection + ? { + name: 'triggerData', + type: 'item', + key: 'triggerData', + title: tval('Trigger data'), + Component: CollectionBlockInitializer, + collection: config.collection, + dataPath: '$context.data', + } + : null; + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/index.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/index.tsx index 9177c9f39..6fcb27279 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/index.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/index.tsx @@ -1,226 +1,11 @@ -import { - Plugin, - useActionContext, - useBlockRequestContext, - useCollectionDataSource, - useCollectionManager_deprecated, - useCollectValuesToSubmit, - useCompile, - useFilterByTk, -} from '@tachybase/client'; -import { useField, useFieldSchema, useForm } from '@tachybase/schema'; -import { isURL } from '@tachybase/utils/client'; +import { Plugin } from '@tachybase/client'; -import { App, message } from 'antd'; -import { useNavigate } from 'react-router-dom'; - -import { CollectionBlockInitializer } from '../../components'; -import { lang, tval } from '../../locale'; +import { tval } from '../../locale'; import { PluginWorkflow } from '../../Plugin'; -import { Trigger } from '../../triggers'; -import { getCollectionFieldOptions, UseVariableOptions } from '../../variable'; +import { OmniActionTrigger } from './OmniActionTrigger'; +import { useFormWorkflowCustomActionProps } from './useFormWorkflowCustomActionProps'; +import { useRecordWorkflowCustomTriggerActionProps } from './useRecordWorkflowCustomTriggerActionProps'; -class OmniAction extends Trigger { - title = tval('Custom action event'); - description = tval( - `When the "Trigger Workflow" button is clicked, the event is triggered based on the single piece of data where the button is located. For complex data processing that cannot be handled simply by NocoBase\\'s built-in operations (CRUD), you can define a series of operations through a workflow and trigger it with the "Trigger Workflow" button.`, - ); - fieldset = { - collection: { - type: 'string', - title: tval('Collection'), - required: true, - 'x-decorator': 'FormItem', - 'x-component': 'DataSourceCollectionCascader', - 'x-reactions': [{ target: 'changed', effects: ['onFieldValueChange'], fulfill: { state: { value: [] } } }], - }, - appends: { - type: 'array', - title: tval('Associations to use'), - description: tval( - '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.', - ), - 'x-decorator': 'FormItem', - 'x-component': 'AppendsTreeSelect', - 'x-component-props': { - multiple: true, - useCollection() { - const form = useForm(); - return form.values?.collection; - }, - }, - 'x-reactions': [{ dependencies: ['collection'], fulfill: { state: { visible: '{{!!$deps[0]}}' } } }], - }, - }; - scope = { useCollectionDataSource }; - components = {}; - isActionTriggerable = (config, context) => { - return context.buttonAction === 'customize:triggerWorkflows'; - }; - useVariables(config: Record, options?: UseVariableOptions) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const compile = useCompile(); - // eslint-disable-next-line react-hooks/rules-of-hooks - const { getCollectionFields: getCollectionFields } = useCollectionManager_deprecated(); - const m = getCollectionFieldOptions({ - appends: ['user'], - ...options, - fields: [ - { - collectionName: 'users', - name: 'user', - type: 'hasOne', - target: 'users', - uiSchema: { title: lang('User acted') }, - }, - ], - compile, - getCollectionFields, - }); - return [ - ...getCollectionFieldOptions({ - appends: ['data', ...(config.appends?.map((append) => `data.${append}`) || [])], - fields: [ - { - collectionName: config.collection, - name: 'data', - type: 'hasOne', - target: config.collection, - uiSchema: { title: lang('Trigger data') }, - }, - ], - compile, - getCollectionFields, - }), - ...m, - { label: lang('Role of user acted'), value: 'roleName' }, - ]; - } - useInitializers(config) { - return config.collection - ? { - name: 'triggerData', - type: 'item', - key: 'triggerData', - title: tval('Trigger data'), - Component: CollectionBlockInitializer, - collection: config.collection, - dataPath: '$context.data', - } - : null; - } -} -function useFormWorkflowCustomActionProps() { - const form = useForm(); - const { field, __parent, resource } = useBlockRequestContext(); - const { setVisible } = useActionContext(); - const filterByTk = useFilterByTk(); - const navigate = useNavigate(); - const fieldSchema = useFieldSchema(); - const _field = useField(); - const compile = useCompile(); - const { modal } = App.useApp(); - const collectionValuesToSubmit = useCollectValuesToSubmit(); - return ( - _field.componentProps.filterKeys, - { - async onClick() { - const { onSuccess, skipValidator, triggerWorkflows } = fieldSchema?.['x-action-settings'] || {}; - if (!skipValidator) { - await form.submit(); - } - const values = await collectionValuesToSubmit(); - (_field.data = field.data || {}), (_field.data.loading = true); - try { - const B = await resource.trigger({ - values, - filterByTk, - triggerWorkflows: triggerWorkflows?.length - ? triggerWorkflows - .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) - .join(',') - : void 0, - }); - if ( - ((_field.data.data = B), - __parent?.service?.refresh?.(), - setVisible == null || setVisible(false), - !(onSuccess != null && onSuccess.successMessage)) - ) - return; - onSuccess != null && onSuccess.manualClose - ? modal.success({ - title: compile(onSuccess?.successMessage), - onOk: async () => { - await form.reset(); - return ( - onSuccess?.redirecting && - onSuccess?.redirectTo && - (isURL(onSuccess.redirectTo) - ? (window.location.href = onSuccess.redirectTo) - : navigate(onSuccess.redirectTo)) - ); - }, - }) - : message.success(compile(onSuccess == null ? void 0 : onSuccess.successMessage)); - } catch (B) { - console.error(B); - } finally { - _field.data.loading = false; - } - }, - } - ); -} -function useRecordWorkflowCustomTriggerActionProps() { - const compile = useCompile(); - const filterByTk = useFilterByTk(); - const _field = useField(); - const fieldSchema = useFieldSchema(); - const { field, resource } = useBlockRequestContext(); - const { setVisible, setSubmitted } = useActionContext() as any; - const { modal } = App.useApp(); - const navigate = useNavigate(); - const { onSuccess, triggerWorkflows } = fieldSchema?.['x-action-settings'] || {}; - return { - async onClick(N, w) { - (_field.data = field.data || {}), (_field.data.loading = true); - try { - if ( - (await resource.trigger({ - filterByTk: filterByTk, - triggerWorkflows: triggerWorkflows?.length - ? triggerWorkflows - .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) - .join(',') - : void 0, - }), - w && w(), - setVisible == null || setVisible(false), - setSubmitted == null || setSubmitted(true), - !onSuccess?.successMessage) - ) - return; - if (onSuccess?.manualClose) { - modal.success({ - title: compile(onSuccess?.successMessage), - onOk() { - onSuccess?.redirecting && - onSuccess?.redirectTo && - (isURL(onSuccess.redirectTo) - ? (window.location.href = onSuccess.redirectTo) - : navigate(onSuccess.redirectTo)); - }, - }); - } else message.success(compile(onSuccess?.successMessage)); - } catch (error) { - console.error(error); - } finally { - _field.data.loading = false; - } - }, - }; -} const triggerWorkflowItem = { name: 'triggerWorkflow', title: tval('Trigger workflow'), @@ -264,7 +49,7 @@ const triggerWorkflowLinkItem = { }; export class PluginOmniTrigger extends Plugin { async load() { - this.app.pm.get('workflow').registerTrigger('custom-action', OmniAction); + this.app.pm.get('workflow').registerTrigger('omni-action', OmniActionTrigger); this.app.addScopes({ useFormWorkflowCustomActionProps, useRecordWorkflowCustomTriggerActionProps, diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useFormWorkflowCustomActionProps.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useFormWorkflowCustomActionProps.tsx new file mode 100644 index 000000000..a77789bcc --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useFormWorkflowCustomActionProps.tsx @@ -0,0 +1,76 @@ +import { + useActionContext, + useBlockRequestContext, + useCollectValuesToSubmit, + useCompile, + useFilterByTk, +} from '@tachybase/client'; +import { useField, useFieldSchema, useForm } from '@tachybase/schema'; +import { isURL } from '@tachybase/utils/client'; + +import { App, message } from 'antd'; +import { useNavigate } from 'react-router-dom'; + +export function useFormWorkflowCustomActionProps() { + const form = useForm(); + const { field, __parent, resource } = useBlockRequestContext(); + const { setVisible } = useActionContext(); + const filterByTk = useFilterByTk(); + const navigate = useNavigate(); + const fieldSchema = useFieldSchema(); + const _field = useField(); + const compile = useCompile(); + const { modal } = App.useApp(); + const collectionValuesToSubmit = useCollectValuesToSubmit(); + return ( + _field.componentProps.filterKeys, + { + async onClick() { + const { onSuccess, skipValidator, triggerWorkflows } = fieldSchema?.['x-action-settings'] || {}; + if (!skipValidator) { + await form.submit(); + } + const values = await collectionValuesToSubmit(); + _field.data = field.data || {}; + _field.data.loading = true; + try { + const result = await resource.trigger({ + values, + filterByTk, + triggerWorkflows: triggerWorkflows?.length + ? triggerWorkflows + .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) + .join(',') + : undefined, + }); + _field.data.data = result; + __parent?.service?.refresh?.(); + setVisible?.(false); + if (!onSuccess?.successMessage) { + return; + } + + onSuccess != null && onSuccess.manualClose + ? modal.success({ + title: compile(onSuccess?.successMessage), + onOk: async () => { + await form.reset(); + return ( + onSuccess?.redirecting && + onSuccess?.redirectTo && + (isURL(onSuccess.redirectTo) + ? (window.location.href = onSuccess.redirectTo) + : navigate(onSuccess.redirectTo)) + ); + }, + }) + : message.success(compile(onSuccess == null ? void 0 : onSuccess.successMessage)); + } catch (error) { + console.error(error); + } finally { + _field.data.loading = false; + } + }, + } + ); +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useRecordWorkflowCustomTriggerActionProps.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useRecordWorkflowCustomTriggerActionProps.tsx new file mode 100644 index 000000000..96746eaa8 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/omni-trigger/useRecordWorkflowCustomTriggerActionProps.tsx @@ -0,0 +1,55 @@ +import { useActionContext, useBlockRequestContext, useCompile, useFilterByTk } from '@tachybase/client'; +import { useField, useFieldSchema } from '@tachybase/schema'; +import { isURL } from '@tachybase/utils/client'; + +import { App, message } from 'antd'; +import { useNavigate } from 'react-router-dom'; + +export function useRecordWorkflowCustomTriggerActionProps() { + const compile = useCompile(); + const filterByTk = useFilterByTk(); + const _field = useField(); + const fieldSchema = useFieldSchema(); + const { field, resource } = useBlockRequestContext(); + const { setVisible, setSubmitted } = useActionContext() as any; + const { modal } = App.useApp(); + const navigate = useNavigate(); + const { onSuccess, triggerWorkflows } = fieldSchema?.['x-action-settings'] || {}; + return { + async onClick(e?, callback?) { + (_field.data = field.data || {}), (_field.data.loading = true); + try { + await resource.trigger({ + filterByTk: filterByTk, + triggerWorkflows: triggerWorkflows?.length + ? triggerWorkflows + .map((workflow) => [workflow.workflowKey, workflow.context].filter(Boolean).join('!')) + .join(',') + : undefined, + }); + callback?.(); + setVisible?.(false); + setSubmitted?.(true); + if (!onSuccess?.successMessage) { + return; + } + if (onSuccess?.manualClose) { + modal.success({ + title: compile(onSuccess?.successMessage), + onOk() { + onSuccess?.redirecting && + onSuccess?.redirectTo && + (isURL(onSuccess.redirectTo) + ? (window.location.href = onSuccess.redirectTo) + : navigate(onSuccess.redirectTo)); + }, + }); + } else message.success(compile(onSuccess?.successMessage)); + } catch (error) { + console.error(error); + } finally { + _field.data.loading = false; + } + }, + }; +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/response/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/response/index.ts index c491c4446..73b30f8a4 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/features/response/index.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/response/index.ts @@ -27,7 +27,9 @@ class ResponseInstruction extends Instruction { WorkflowVariableInput, }; isAvailable({ workflow }) { - return workflow.type === 'request-interception' || ('action' === workflow.type && workflow.sync); + return ( + workflow.type === 'request-interception' || (['action', 'omni-action'].includes(workflow.type) && workflow.sync) + ); } } export class PluginResponse extends Plugin { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/locale/en-US.json b/packages/plugins/@tachybase/plugin-workflow/src/locale/en-US.json index ff86ebe8d..ca44bcf89 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/locale/en-US.json +++ b/packages/plugins/@tachybase/plugin-workflow/src/locale/en-US.json @@ -1,5 +1,6 @@ { "Workflow": "Workflow", + "Data will be updated": "Data will be updated", "Execution history": "Execution history", "Executed": "Executed", "Trigger type": "Trigger type", @@ -12,6 +13,7 @@ "Loading": "Loading", "Load failed": "Load failed", "Trigger": "Trigger", + "Trigger workflow": "Trigger workflow", "Trigger variables": "Trigger variables", "Trigger data": "Trigger data", "Trigger time": "Trigger time", @@ -190,5 +192,6 @@ "Ignore failed request and continue workflow": "Ignore failed request and continue workflow", "SQL action": "SQL action", "Execute a SQL statement in database": "Execute a SQL statement in database", + "Omni action event": "Omni action event", "Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet." -} \ 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 a07773644..f726b6e6b 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json +++ b/packages/plugins/@tachybase/plugin-workflow/src/locale/zh-CN.json @@ -1,6 +1,7 @@ { "Workflow": "工作流", "Execution history": "执行历史", + "Data will be updated": "更新的数据", "Clear all executions": "清除所有执行记录", "Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?": "清空执行记录不会重置执行次数,且执行中的也不会被删除,确定要删除所有执行记录吗?", "Executed": "已执行", @@ -21,6 +22,7 @@ "Auto delete history when execution is on end status": "执行结束后自动删除对应状态的历史记录", "Trigger": "触发器", "Trigger variables": "触发器变量", + "Trigger workflow": "触发工作流", "Trigger data": "触发器数据", "Trigger time": "触发时间", "Triggered at": "触发时间", @@ -256,6 +258,7 @@ "Ignore failed request and continue workflow": "忽略失败的请求并继续工作流", "SQL action": "SQL 操作", "Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句", + "Omni action event": "万能操作事件", "Select a data source to execute SQL.": "选择一个数据源来执行 SQL", "SQL query result could be used through <1>JSON query node (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点 中使用(商业插件)。" -} \ No newline at end of file +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/CustomActionTrigger.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/CustomActionTrigger.ts index fc0291d9a..90a1a50b6 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/CustomActionTrigger.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/CustomActionTrigger.ts @@ -1,7 +1,7 @@ import { joinCollectionName, parseCollectionName } from '@tachybase/data-source-manager'; import PluginErrorHandler from '@tachybase/plugin-error-handler'; -import _ from 'lodash'; +import _, { isArray } from 'lodash'; import { EXECUTION_STATUS } from '../../constants'; import Trigger from '../../triggers'; @@ -16,12 +16,12 @@ class CustomActionInterceptionError extends Error { } } export class OmniTrigger extends Trigger { - static TYPE = 'custom-action'; + static TYPE = 'omni-action'; triggerAction = async (context, next) => { const { resourceName, actionName, - params: { filterByTk, values, triggerWorkflows = '' }, + params: { filterByTk, values, triggerWorkflows = '', filter }, } = context.action; if (actionName !== 'trigger' || resourceName === 'workflows') { return next(); @@ -70,7 +70,17 @@ export class OmniTrigger extends Trigger { const formData = dataPath ? _.get(values, dataPath) : values; let data = formData; if (filterByTk != null) { - data = await repository.findOne({ filterByTk, appends }); + if (isArray(filterByTk)) { + data = await repository.find({ filterByTk, appends }); + } else { + data = await repository.findOne({ filterByTk, appends }); + } + if (!data) { + continue; + } + Object.assign(data, formData); + } else if (filter != null) { + data = await repository.find({ filter, appends }); if (!data) { continue; } diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/Plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/Plugin.ts index 96de47713..24751f6eb 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/Plugin.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/omni-trigger/Plugin.ts @@ -6,6 +6,6 @@ import { OmniTrigger } from './CustomActionTrigger'; export class PluginOmniTrigger extends Plugin { async load() { const workflowPlugin = this.app.pm.get(PluginWorkflowServer) as PluginWorkflowServer; - workflowPlugin.registerTrigger('custom-action', OmniTrigger); + workflowPlugin.registerTrigger('omni-action', OmniTrigger); } } diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/index.ts index 5a5c6f40a..0590ba29f 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/index.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/index.ts @@ -4,4 +4,5 @@ export * from './instructions'; export { Trigger } from './triggers'; export { default as Processor } from './Processor'; export { default } from './Plugin'; +export { default as PluginWorkflow } from './Plugin'; export * from './types';