feat: webhook can trigger workflows (#1193)
Reviewed-on: daoyoucloud/tachybase#1193
This commit is contained in:
parent
31d9233c32
commit
e74de20f1b
@ -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 { t } = useTranslation();
|
||||||
const index = ArrayTable.useIndex();
|
const index = ArrayTable.useIndex();
|
||||||
const { setValuesIn } = useForm();
|
const { setValuesIn } = useForm();
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ExtendCollectionsProvider, SchemaComponent } from '@tachybase/client';
|
import { ExtendCollectionsProvider, SchemaComponent, WorkflowSelect } from '@tachybase/client';
|
||||||
import { ISchema } from '@tachybase/schema';
|
import { ISchema } from '@tachybase/schema';
|
||||||
|
|
||||||
import { tval } from '../locale';
|
import { tval } from '../locale';
|
||||||
|
|
||||||
const collection = {
|
const collection = {
|
||||||
name: 'webhooks',
|
name: 'webhooks',
|
||||||
|
title: 'webhooks',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -34,6 +35,21 @@ const collection = {
|
|||||||
default: false,
|
default: false,
|
||||||
} as ISchema,
|
} 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',
|
type: 'string',
|
||||||
name: 'type',
|
name: 'type',
|
||||||
@ -239,17 +255,11 @@ const schema: ISchema = {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Grid.Col',
|
'x-component': 'Grid.Col',
|
||||||
properties: {
|
properties: {
|
||||||
workflow: {
|
workflowKey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-collection-field': 'webhooks.workflow',
|
'x-collection-field': 'webhooks.workflowKey',
|
||||||
'x-component-props': {
|
|
||||||
fieldNames: {
|
|
||||||
value: 'id',
|
|
||||||
label: 'name',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -373,17 +383,9 @@ const schema: ISchema = {
|
|||||||
'x-decorator': 'TableV2.Column.Decorator',
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
'x-component': 'TableV2.Column',
|
'x-component': 'TableV2.Column',
|
||||||
properties: {
|
properties: {
|
||||||
workflow: {
|
workflowKey: {
|
||||||
'x-collection-field': 'webhooks.workflow',
|
'x-collection-field': 'webhooks.workflowKey',
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-component-props': {
|
|
||||||
fieldNames: {
|
|
||||||
value: 'id',
|
|
||||||
label: 'name',
|
|
||||||
},
|
|
||||||
ellipsis: true,
|
|
||||||
size: 'small',
|
|
||||||
},
|
|
||||||
'x-read-pretty': true,
|
'x-read-pretty': true,
|
||||||
'x-decorator': null,
|
'x-decorator': null,
|
||||||
'x-decorator-props': {
|
'x-decorator-props': {
|
||||||
@ -575,17 +577,11 @@ const schema: ISchema = {
|
|||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Grid.Col',
|
'x-component': 'Grid.Col',
|
||||||
properties: {
|
properties: {
|
||||||
workflow: {
|
workflowKey: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-collection-field': 'webhooks.workflow',
|
'x-collection-field': 'webhooks.workflowKey',
|
||||||
'x-component-props': {
|
|
||||||
fieldNames: {
|
|
||||||
value: 'id',
|
|
||||||
label: 'name',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -681,7 +677,7 @@ const schema: ISchema = {
|
|||||||
export const WebhookManager = () => {
|
export const WebhookManager = () => {
|
||||||
return (
|
return (
|
||||||
<ExtendCollectionsProvider collections={[collection]}>
|
<ExtendCollectionsProvider collections={[collection]}>
|
||||||
<SchemaComponent memoized schema={schema}></SchemaComponent>
|
<SchemaComponent memoized schema={schema} components={{ WorkflowSelect }}></SchemaComponent>
|
||||||
</ExtendCollectionsProvider>
|
</ExtendCollectionsProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Context } from '@tachybase/actions';
|
import { Context } from '@tachybase/actions';
|
||||||
|
import { PluginWorkflow } from '@tachybase/plugin-workflow';
|
||||||
import { Action, Controller } from '@tachybase/utils';
|
import { Action, Controller } from '@tachybase/utils';
|
||||||
|
|
||||||
@Controller('webhooks')
|
@Controller('webhooks')
|
||||||
@ -19,18 +20,33 @@ export class WebhookController {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error('not support');
|
throw new Error('not support');
|
||||||
}
|
}
|
||||||
|
const pluginWorkflow = ctx.app.getPlugin(PluginWorkflow) as PluginWorkflow;
|
||||||
const repo = ctx.db.getRepository('webhooks');
|
const repo = ctx.db.getRepository('webhooks');
|
||||||
const webhook = await repo.findOne(where);
|
const webhook = await repo.findOne(where);
|
||||||
const webhookCtx = {
|
const webhookCtx = {
|
||||||
|
request: ctx.request,
|
||||||
action: ctx.action,
|
action: ctx.action,
|
||||||
body: '',
|
body: '',
|
||||||
};
|
};
|
||||||
run(webhook.code, {
|
run(webhook.code, {
|
||||||
ctx: webhookCtx,
|
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) {
|
if (webhookCtx.body) {
|
||||||
ctx.withoutDataWrapping = true;
|
ctx.withoutDataWrapping = true;
|
||||||
ctx.body = webhookCtx.body;
|
ctx.body = webhookCtx.body;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export const APIRegularInitializer = () => {
|
|||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
title: tval('Regular workflow'),
|
title: tval('Trigger workflow'),
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
'x-use-component-props': 'usePropsAPIRegular',
|
'x-use-component-props': 'usePropsAPIRegular',
|
||||||
'x-align': 'right',
|
'x-align': 'right',
|
||||||
@ -21,10 +21,11 @@ const schema = {
|
|||||||
},
|
},
|
||||||
'x-toolbar': 'ActionSchemaToolbar',
|
'x-toolbar': 'ActionSchemaToolbar',
|
||||||
'x-settings': 'actionSettings:APIRegular',
|
'x-settings': 'actionSettings:APIRegular',
|
||||||
'x-action': 'customize:APIRegular',
|
'x-action': 'customize:triggerWorkflows',
|
||||||
'x-action-settings': {
|
'x-action-settings': {
|
||||||
bindWorkflow: false,
|
bindWorkflow: false,
|
||||||
updateMode: 'selected',
|
updateMode: 'selected',
|
||||||
|
triggerWorkflows: [],
|
||||||
},
|
},
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
icon: 'CarryOutOutlined',
|
icon: 'CarryOutOutlined',
|
||||||
|
@ -5,8 +5,9 @@ import {
|
|||||||
useDesignable,
|
useDesignable,
|
||||||
useRequest,
|
useRequest,
|
||||||
useSchemaToolbar,
|
useSchemaToolbar,
|
||||||
|
WorkflowConfig,
|
||||||
} from '@tachybase/client';
|
} from '@tachybase/client';
|
||||||
import { useFieldSchema } from '@tachybase/schema';
|
import { isValid, useFieldSchema } from '@tachybase/schema';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
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',
|
name: 'Bind workflow',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
@ -74,9 +74,11 @@ export const usePropsAPIRegular = () => {
|
|||||||
async onClick() {
|
async onClick() {
|
||||||
const selectedRecordKeys =
|
const selectedRecordKeys =
|
||||||
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? [];
|
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? [];
|
||||||
|
const { filter } = service.params?.[0] ?? {};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
bindWorkflow = false,
|
bindWorkflow = false,
|
||||||
|
triggerWorkflows = false,
|
||||||
assignedValues: originalAssignedValues = {},
|
assignedValues: originalAssignedValues = {},
|
||||||
updateMode,
|
updateMode,
|
||||||
} = actionSchema?.['x-action-settings'] ?? {};
|
} = actionSchema?.['x-action-settings'] ?? {};
|
||||||
@ -90,7 +92,7 @@ export const usePropsAPIRegular = () => {
|
|||||||
actionField.data = field.data || {};
|
actionField.data = field.data || {};
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
|
|
||||||
if (!bindWorkflow) {
|
if (!triggerWorkflows && !bindWorkflow) {
|
||||||
return modal.info({
|
return modal.info({
|
||||||
title: lang('Not bind workflow!'),
|
title: lang('Not bind workflow!'),
|
||||||
});
|
});
|
||||||
@ -122,11 +124,36 @@ export const usePropsAPIRegular = () => {
|
|||||||
title: lang('Confirm', { ns: 'client' }),
|
title: lang('Confirm', { ns: 'client' }),
|
||||||
content: lang('Trigger workflow?', { ns: 'client' }),
|
content: lang('Trigger workflow?', { ns: 'client' }),
|
||||||
async onOk() {
|
async onOk() {
|
||||||
|
if (bindWorkflow) {
|
||||||
const params = {
|
const params = {
|
||||||
filterByTk: bindWorkflow,
|
filterByTk: bindWorkflow,
|
||||||
updateData: encodeURIComponent(JSON.stringify(updateData)),
|
updateData: encodeURIComponent(JSON.stringify(updateData)),
|
||||||
};
|
};
|
||||||
run(params);
|
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;
|
actionField.data.loading = false;
|
||||||
},
|
},
|
||||||
async onCancel() {
|
async onCancel() {
|
||||||
|
@ -19,7 +19,7 @@ export class KitAPIRegularUsage extends Plugin {
|
|||||||
|
|
||||||
['table', 'details'].forEach((block) => {
|
['table', 'details'].forEach((block) => {
|
||||||
this.app.schemaInitializerManager.addItem(`${block}:configureActions`, 'customize.APIRegular', {
|
this.app.schemaInitializerManager.addItem(`${block}:configureActions`, 'customize.APIRegular', {
|
||||||
title: tval('Regular workflow'),
|
title: tval('Trigger workflow'),
|
||||||
Component: 'APIRegularInitializer',
|
Component: 'APIRegularInitializer',
|
||||||
name: 'apiRegular',
|
name: 'apiRegular',
|
||||||
useVisible() {
|
useVisible() {
|
||||||
|
@ -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<string, any>, 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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,226 +1,11 @@
|
|||||||
import {
|
import { Plugin } from '@tachybase/client';
|
||||||
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 { App, message } from 'antd';
|
import { tval } from '../../locale';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { CollectionBlockInitializer } from '../../components';
|
|
||||||
import { lang, tval } from '../../locale';
|
|
||||||
import { PluginWorkflow } from '../../Plugin';
|
import { PluginWorkflow } from '../../Plugin';
|
||||||
import { Trigger } from '../../triggers';
|
import { OmniActionTrigger } from './OmniActionTrigger';
|
||||||
import { getCollectionFieldOptions, UseVariableOptions } from '../../variable';
|
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<string, any>, 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 = {
|
const triggerWorkflowItem = {
|
||||||
name: 'triggerWorkflow',
|
name: 'triggerWorkflow',
|
||||||
title: tval('Trigger workflow'),
|
title: tval('Trigger workflow'),
|
||||||
@ -264,7 +49,7 @@ const triggerWorkflowLinkItem = {
|
|||||||
};
|
};
|
||||||
export class PluginOmniTrigger extends Plugin {
|
export class PluginOmniTrigger extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
this.app.pm.get<PluginWorkflow>('workflow').registerTrigger('custom-action', OmniAction);
|
this.app.pm.get<PluginWorkflow>('workflow').registerTrigger('omni-action', OmniActionTrigger);
|
||||||
this.app.addScopes({
|
this.app.addScopes({
|
||||||
useFormWorkflowCustomActionProps,
|
useFormWorkflowCustomActionProps,
|
||||||
useRecordWorkflowCustomTriggerActionProps,
|
useRecordWorkflowCustomTriggerActionProps,
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -27,7 +27,9 @@ class ResponseInstruction extends Instruction {
|
|||||||
WorkflowVariableInput,
|
WorkflowVariableInput,
|
||||||
};
|
};
|
||||||
isAvailable({ workflow }) {
|
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 {
|
export class PluginResponse extends Plugin {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Workflow": "Workflow",
|
"Workflow": "Workflow",
|
||||||
|
"Data will be updated": "Data will be updated",
|
||||||
"Execution history": "Execution history",
|
"Execution history": "Execution history",
|
||||||
"Executed": "Executed",
|
"Executed": "Executed",
|
||||||
"Trigger type": "Trigger type",
|
"Trigger type": "Trigger type",
|
||||||
@ -12,6 +13,7 @@
|
|||||||
"Loading": "Loading",
|
"Loading": "Loading",
|
||||||
"Load failed": "Load failed",
|
"Load failed": "Load failed",
|
||||||
"Trigger": "Trigger",
|
"Trigger": "Trigger",
|
||||||
|
"Trigger workflow": "Trigger workflow",
|
||||||
"Trigger variables": "Trigger variables",
|
"Trigger variables": "Trigger variables",
|
||||||
"Trigger data": "Trigger data",
|
"Trigger data": "Trigger data",
|
||||||
"Trigger time": "Trigger time",
|
"Trigger time": "Trigger time",
|
||||||
@ -190,5 +192,6 @@
|
|||||||
"Ignore failed request and continue workflow": "Ignore failed request and continue workflow",
|
"Ignore failed request and continue workflow": "Ignore failed request and continue workflow",
|
||||||
"SQL action": "SQL action",
|
"SQL action": "SQL action",
|
||||||
"Execute a SQL statement in database": "Execute a SQL statement in database",
|
"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."
|
"Usage of SQL query result is not supported yet.": "Usage of SQL query result is not supported yet."
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Workflow": "工作流",
|
"Workflow": "工作流",
|
||||||
"Execution history": "执行历史",
|
"Execution history": "执行历史",
|
||||||
|
"Data will be updated": "更新的数据",
|
||||||
"Clear all executions": "清除所有执行记录",
|
"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?": "清空执行记录不会重置执行次数,且执行中的也不会被删除,确定要删除所有执行记录吗?",
|
"Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?": "清空执行记录不会重置执行次数,且执行中的也不会被删除,确定要删除所有执行记录吗?",
|
||||||
"Executed": "已执行",
|
"Executed": "已执行",
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"Auto delete history when execution is on end status": "执行结束后自动删除对应状态的历史记录",
|
"Auto delete history when execution is on end status": "执行结束后自动删除对应状态的历史记录",
|
||||||
"Trigger": "触发器",
|
"Trigger": "触发器",
|
||||||
"Trigger variables": "触发器变量",
|
"Trigger variables": "触发器变量",
|
||||||
|
"Trigger workflow": "触发工作流",
|
||||||
"Trigger data": "触发器数据",
|
"Trigger data": "触发器数据",
|
||||||
"Trigger time": "触发时间",
|
"Trigger time": "触发时间",
|
||||||
"Triggered at": "触发时间",
|
"Triggered at": "触发时间",
|
||||||
@ -256,6 +258,7 @@
|
|||||||
"Ignore failed request and continue workflow": "忽略失败的请求并继续工作流",
|
"Ignore failed request and continue workflow": "忽略失败的请求并继续工作流",
|
||||||
"SQL action": "SQL 操作",
|
"SQL action": "SQL 操作",
|
||||||
"Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句",
|
"Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句",
|
||||||
|
"Omni action event": "万能操作事件",
|
||||||
"Select a data source to execute SQL.": "选择一个数据源来执行 SQL",
|
"Select a data source to execute SQL.": "选择一个数据源来执行 SQL",
|
||||||
"SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用(商业插件)。"
|
"SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用(商业插件)。"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { joinCollectionName, parseCollectionName } from '@tachybase/data-source-manager';
|
import { joinCollectionName, parseCollectionName } from '@tachybase/data-source-manager';
|
||||||
import PluginErrorHandler from '@tachybase/plugin-error-handler';
|
import PluginErrorHandler from '@tachybase/plugin-error-handler';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _, { isArray } from 'lodash';
|
||||||
|
|
||||||
import { EXECUTION_STATUS } from '../../constants';
|
import { EXECUTION_STATUS } from '../../constants';
|
||||||
import Trigger from '../../triggers';
|
import Trigger from '../../triggers';
|
||||||
@ -16,12 +16,12 @@ class CustomActionInterceptionError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class OmniTrigger extends Trigger {
|
export class OmniTrigger extends Trigger {
|
||||||
static TYPE = 'custom-action';
|
static TYPE = 'omni-action';
|
||||||
triggerAction = async (context, next) => {
|
triggerAction = async (context, next) => {
|
||||||
const {
|
const {
|
||||||
resourceName,
|
resourceName,
|
||||||
actionName,
|
actionName,
|
||||||
params: { filterByTk, values, triggerWorkflows = '' },
|
params: { filterByTk, values, triggerWorkflows = '', filter },
|
||||||
} = context.action;
|
} = context.action;
|
||||||
if (actionName !== 'trigger' || resourceName === 'workflows') {
|
if (actionName !== 'trigger' || resourceName === 'workflows') {
|
||||||
return next();
|
return next();
|
||||||
@ -70,7 +70,17 @@ export class OmniTrigger extends Trigger {
|
|||||||
const formData = dataPath ? _.get(values, dataPath) : values;
|
const formData = dataPath ? _.get(values, dataPath) : values;
|
||||||
let data = formData;
|
let data = formData;
|
||||||
if (filterByTk != null) {
|
if (filterByTk != null) {
|
||||||
|
if (isArray(filterByTk)) {
|
||||||
|
data = await repository.find({ filterByTk, appends });
|
||||||
|
} else {
|
||||||
data = await repository.findOne({ filterByTk, appends });
|
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) {
|
if (!data) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@ import { OmniTrigger } from './CustomActionTrigger';
|
|||||||
export class PluginOmniTrigger extends Plugin {
|
export class PluginOmniTrigger extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
const workflowPlugin = this.app.pm.get(PluginWorkflowServer) as PluginWorkflowServer;
|
const workflowPlugin = this.app.pm.get(PluginWorkflowServer) as PluginWorkflowServer;
|
||||||
workflowPlugin.registerTrigger('custom-action', OmniTrigger);
|
workflowPlugin.registerTrigger('omni-action', OmniTrigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,5 @@ export * from './instructions';
|
|||||||
export { Trigger } from './triggers';
|
export { Trigger } from './triggers';
|
||||||
export { default as Processor } from './Processor';
|
export { default as Processor } from './Processor';
|
||||||
export { default } from './Plugin';
|
export { default } from './Plugin';
|
||||||
|
export { default as PluginWorkflow } from './Plugin';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
Loading…
Reference in New Issue
Block a user