refactor: webhooks rename to dispatchers (#1385)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#1385
This commit is contained in:
sealday 2024-07-25 15:24:35 +08:00
parent f717663d9e
commit 730c7aef5b
10 changed files with 304 additions and 310 deletions

View File

@ -107,6 +107,12 @@ export class PluginWorkflow extends Plugin {
this.addComponents();
this.app.pluginSettingsManager.add(NAMESPACE, {
icon: 'PartitionOutlined',
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
aclSnippet: 'pm.workflow.workflows',
});
this.app.pluginSettingsManager.add(NAMESPACE + '.list', {
icon: 'PartitionOutlined',
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
Component: WorkflowPane,

View File

@ -1,168 +0,0 @@
import {
SchemaInitializerItemType,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@tachybase/client';
import {
CheckboxGroupWithTooltip,
CollectionBlockInitializer,
FieldsSelect,
getCollectionFieldOptions,
RadioWithTooltip,
} from '../../..';
import { lang, tval } from '../../../locale';
import { Trigger } from '../../../triggers';
const enum ACTION_TYPES {
CREATE = 'create',
UPDATE = 'update',
UPSERT = 'updateOrCreate',
DESTROY = 'destroy',
}
export class APIRegularTrigger extends Trigger {
title = lang('API Regular(deprecated)');
description = lang('Trigger when an API call is made.');
fieldset = {
collection: {
type: 'string',
title: tval('Collection'),
required: true,
'x-decorator': 'FormItem',
'x-component': 'DataSourceCollectionCascader',
'x-reactions': [
{
target: 'changed',
effects: ['onFieldValueChange'],
fulfill: {
state: {
value: [],
},
},
},
],
},
global: {
type: 'boolean',
title: tval('Trigger mode'),
'x-decorator': 'FormItem',
'x-component': 'RadioWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{
label: tval('Local mode, triggered before executing the actions bound to this workflow'),
value: false,
},
{
label: tval('Global mode, triggered before executing the following actions'),
value: true,
},
],
},
default: false,
},
actions: {
type: 'number',
title: tval('Select actions'),
'x-decorator': 'FormItem',
'x-component': 'CheckboxGroupWithTooltip',
'x-component-props': {
direction: 'vertical',
options: [
{ label: tval('Create record action'), value: ACTION_TYPES.CREATE },
{ label: tval('Update record action'), value: ACTION_TYPES.UPDATE },
{ label: tval('Delete record action'), value: ACTION_TYPES.DESTROY },
],
},
required: true,
'x-reactions': [
{
dependencies: ['collection', 'global'],
fulfill: {
state: {
visible: '{{!!$deps[0] && !!$deps[1]}}',
},
},
},
],
},
};
scope = { useCollectionDataSource };
components = {
FieldsSelect: FieldsSelect,
RadioWithTooltip: RadioWithTooltip,
CheckboxGroupWithTooltip: CheckboxGroupWithTooltip,
};
isActionTriggerable = (config, context) => {
const { global } = config;
return !global && !context.direct;
};
useVariables(config, options) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const compile = useCompile();
// eslint-disable-next-line react-hooks/rules-of-hooks
const { getCollectionFields } = useCollectionManager_deprecated();
// eslint-disable-next-line react-hooks/rules-of-hooks
const langTriggerData = lang('Trigger data');
// eslint-disable-next-line react-hooks/rules-of-hooks
const langUserSubmittedForm = lang('User submitted action');
// eslint-disable-next-line react-hooks/rules-of-hooks
const langRoleSubmittedForm = lang('Role of user submitted action');
const rootFields = [
{
collectionName: config.collection,
name: 'data',
type: 'hasOne',
target: config.collection,
uiSchema: {
title: langTriggerData,
},
},
{
collectionName: 'users',
name: 'user',
type: 'hasOne',
target: 'users',
uiSchema: {
title: langUserSubmittedForm,
},
},
{
name: 'roleName',
uiSchema: {
title: langRoleSubmittedForm,
},
},
];
const result = getCollectionFieldOptions({
// depth,
appends: ['data', 'user', ...(config.appends?.map((item) => `data.${item}`) || [])],
...options,
fields: rootFields,
compile,
getCollectionFields,
});
return result;
}
useInitializers(config): SchemaInitializerItemType | null {
if (!config.collection) {
return null;
}
return {
name: 'triggerData',
type: 'item',
key: 'triggerData',
title: tval('Trigger data'),
Component: CollectionBlockInitializer,
collection: config.collection,
dataPath: '$context.data',
};
}
}

View File

@ -1,12 +1,3 @@
import { Plugin } from '@tachybase/client';
import PluginWorkflow from '../../..';
import { NAMESPACE_TRIGGER_API_REGULAR } from '../../../../common/constants';
import { APIRegularTrigger } from './APIRegular.trigger';
export class KitAPIRegularConfiguration extends Plugin {
async load() {
const pluginWorkflow = this.app.pm.get(PluginWorkflow);
pluginWorkflow.registerTrigger(NAMESPACE_TRIGGER_API_REGULAR, APIRegularTrigger);
}
}
export class KitAPIRegularConfiguration extends Plugin {}

View File

@ -3,7 +3,6 @@ import {
SchemaSettings,
SchemaSettingsItemType,
useDesignable,
useRequest,
useSchemaToolbar,
WorkflowConfig,
} from '@tachybase/client';
@ -11,8 +10,6 @@ import { isValid, useFieldSchema } from '@tachybase/schema';
import { useTranslation } from 'react-i18next';
import { NAMESPACE_TRIGGER_API_REGULAR } from '../../../../common/constants';
const schemaSettingsItems: SchemaSettingsItemType[] = [
{
name: 'editButton',
@ -56,47 +53,6 @@ const schemaSettingsItems: SchemaSettingsItemType[] = [
return isValid(fieldSchema?.['x-action-settings']?.triggerWorkflows);
},
},
{
name: 'Bind workflow',
type: 'select',
useComponentProps() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const { data } = useRequest<any>({
resource: 'workflows',
action: 'list',
params: {
filter: {
type: NAMESPACE_TRIGGER_API_REGULAR,
},
},
});
// FIXME 不会实时生效
const options =
data?.data?.map((workflow) => ({
label: workflow.title,
value: workflow.id,
})) ?? [];
return {
title: t('Bind workflow'),
value: fieldSchema['x-action-settings'].bindWorkflow,
options: options.concat({
label: t('disabled'),
value: false,
}),
onChange(value) {
fieldSchema['x-action-settings'].bindWorkflow = value;
dn.emit('patch', {
schema: {
'x-uid': fieldSchema['x-uid'],
'x-action-settings': fieldSchema['x-action-settings'],
},
});
},
};
},
},
{
name: 'remove',
sort: 100,

View File

@ -1,22 +1,23 @@
import React from 'react';
import {
CollectionOptions,
ExtendCollectionsProvider,
ResourceActionProvider,
SchemaComponent,
useAPIClient,
useRecord,
WorkflowSelect,
} from '@tachybase/client';
import { CodeMirror } from '@tachybase/components';
import { ISchema } from '@tachybase/schema';
import { ISchema, useForm } from '@tachybase/schema';
import { Button, Space } from 'antd';
import { ExecutionStatusColumn } from '../../components/ExecutionStatus';
import OpenDrawer from '../../components/OpenDrawer';
import { ExecutionLink } from '../../ExecutionLink';
import { lang, tval } from '../../locale';
import { lang } from '../../locale';
import { executionSchema } from '../../schemas/executions';
import { dispatchers } from './collections/dispatchers';
export const ExecutionResourceProvider = ({ request, filter = {}, ...others }) => {
const webhook = useRecord();
@ -37,84 +38,22 @@ export const ExecutionResourceProvider = ({ request, filter = {}, ...others }) =
return <ResourceActionProvider {...props} />;
};
export const collection: CollectionOptions = {
name: 'webhooks',
title: 'webhooks',
fields: [
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: tval('Name'),
type: 'string',
'x-component': 'Input',
required: true,
} as ISchema,
},
{
type: 'boolean',
name: 'enabled',
interface: 'radioGroup',
uiSchema: {
title: tval('Enabled'),
type: 'string',
required: true,
enum: [
{ label: tval('On'), value: true },
{ label: tval('Off'), value: false },
],
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: false,
} as ISchema,
},
{
type: 'string',
name: 'workflowKey',
interface: 'select',
uiSchema: {
title: tval('Workflow'),
type: 'string',
'x-component': 'WorkflowSelect',
'x-component-props': {
buttonAction: 'customize:triggerWorkflows',
noCollection: true,
label: 'title',
value: 'key',
export const useTestActionProps = () => {
const form = useForm();
const webhook = useRecord();
const api = useAPIClient();
return {
async onClick() {
const res = await api.resource('webhooks').test({
values: {
body: JSON.parse(form.values.body),
params: JSON.parse(form.values.params),
name: webhook.name,
},
} as ISchema,
});
alert(JSON.stringify(res.data));
},
{
type: 'string',
name: 'type',
interface: 'radioGroup',
uiSchema: {
title: tval('Type'),
type: 'string',
required: true,
enum: [
{ label: tval('Code'), value: 'code' },
{ label: tval('Plugin'), value: 'plugin', disabled: true },
],
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'code',
} as ISchema,
},
{
type: 'text',
name: 'code',
interface: 'textarea',
uiSchema: {
title: tval('Code'),
type: 'string',
'x-component': 'CodeMirror',
default:
'// ctx.query can get user query\n// ctx.body to pass your data to workflow or to client who trigger this webhook.',
} as ISchema,
},
],
};
};
const schema: ISchema = {
@ -126,7 +65,7 @@ const schema: ISchema = {
'x-acl-action': 'webhooks:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection,
collection: dispatchers,
dataSource: 'main',
action: 'list',
params: {
@ -207,7 +146,7 @@ const schema: ISchema = {
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
'x-decorator-props': {
dataSource: 'main',
collection,
collection: dispatchers,
},
'x-component': 'CardItem',
properties: {
@ -382,6 +321,9 @@ const schema: ISchema = {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
'x-component-props': {
width: 50,
},
properties: {
name: {
'x-collection-field': 'webhooks.name',
@ -403,6 +345,9 @@ const schema: ISchema = {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
'x-component-props': {
width: 20,
},
properties: {
enabled: {
'x-collection-field': 'webhooks.enabled',
@ -424,6 +369,9 @@ const schema: ISchema = {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
'x-component-props': {
width: 20,
},
properties: {
workflowKey: {
'x-collection-field': 'webhooks.workflowKey',
@ -455,6 +403,9 @@ const schema: ISchema = {
type: 'void',
'x-decorator': 'TableV2.Column.Decorator',
'x-component': 'TableV2.Column',
'x-component-props': {
width: 20,
},
properties: {
type: {
'x-collection-field': 'webhooks.type',
@ -542,7 +493,7 @@ const schema: ISchema = {
'x-decorator-props': {
action: 'get',
dataSource: 'main',
collection,
collection: dispatchers,
},
'x-component': 'CardItem',
properties: {
@ -722,6 +673,153 @@ const schema: ISchema = {
'x-decorator': 'ACLActionProvider',
type: 'void',
},
test: {
type: 'void',
title: '{{ t("Test") }}',
'x-action': 'update',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
icon: 'EditOutlined',
},
'x-decorator': 'ACLActionProvider',
properties: {
drawer: {
type: 'void',
title: '{{ t("Edit record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
properties: {
tab1: {
type: 'void',
title: '{{t("Edit")}}',
'x-component': 'Tabs.TabPane',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
properties: {
to6mk4v552h: {
type: 'void',
'x-component': 'Grid.Row',
properties: {
sqvdrzdbr7r: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
'8uym9fty5oy': {
type: 'void',
'x-acl-action-props': {
skipScopeCheck: false,
},
'x-acl-action': 'webhooks:update',
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props': 'useEditFormBlockDecoratorProps',
'x-decorator-props': {
action: 'get',
dataSource: 'main',
collection: dispatchers,
},
'x-component': 'CardItem',
properties: {
je28rbthifp: {
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useEditFormBlockProps',
properties: {
yviwt5e73dx: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--tb-spacing)',
},
},
properties: {
unkmoqgvtvr: {
title: '{{ t("Submit") }}',
'x-action': 'submit',
'x-component': 'Action',
'x-use-component-props': 'useTestActionProps',
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
'x-action-settings': {
triggerWorkflows: [],
onSuccess: {
manualClose: false,
redirecting: false,
successMessage: '{{t("Updated successfully")}}',
},
isDeltaChanged: false,
},
type: 'void',
},
},
},
grid: {
type: 'void',
'x-component': 'Grid',
properties: {
'38fm61ehxvn': {
type: 'void',
'x-component': 'Grid.Row',
properties: {
arovreokda8: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
params: {
type: 'string',
'x-component': 'CodeMirror',
'x-component-props': {},
'x-decorator': 'FormItem',
'x-decorator-props': {
label: 'query',
},
},
body: {
type: 'string',
'x-component': 'CodeMirror',
'x-component-props': {},
'x-decorator': 'FormItem',
'x-decorator-props': {
label: 'body',
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
@ -735,10 +833,11 @@ const schema: ISchema = {
export const WebhookManager = () => {
return (
<ExtendCollectionsProvider collections={[collection]}>
<ExtendCollectionsProvider collections={[dispatchers]}>
<SchemaComponent
memoized
schema={schema}
scope={{ useTestActionProps }}
components={{
ExecutionStatusColumn,
ExecutionResourceProvider,

View File

@ -0,0 +1,84 @@
import { CollectionOptions } from '@tachybase/client';
import { ISchema } from '@tachybase/schema';
import { tval } from '../../../locale';
export const dispatchers: CollectionOptions = {
name: 'webhooks',
title: 'webhooks',
fields: [
{
type: 'string',
name: 'name',
interface: 'input',
uiSchema: {
title: tval('Name'),
type: 'string',
'x-component': 'Input',
required: true,
} as ISchema,
},
{
type: 'boolean',
name: 'enabled',
interface: 'radioGroup',
uiSchema: {
title: tval('Enabled'),
type: 'string',
required: true,
enum: [
{ label: tval('On'), value: true },
{ label: tval('Off'), value: false },
],
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: false,
} as ISchema,
},
{
type: 'string',
name: 'workflowKey',
interface: 'select',
uiSchema: {
title: tval('Workflow'),
type: 'string',
'x-component': 'WorkflowSelect',
'x-component-props': {
buttonAction: 'customize:triggerWorkflows',
noCollection: true,
label: 'title',
value: 'key',
},
} as ISchema,
},
{
type: 'string',
name: 'type',
interface: 'radioGroup',
uiSchema: {
title: tval('Type'),
type: 'string',
required: true,
enum: [
{ label: tval('HTTP endpoint'), value: 'code' },
{ label: tval('Scheduler'), value: 'cron' },
],
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'code',
} as ISchema,
},
{
type: 'text',
name: 'code',
interface: 'textarea',
uiSchema: {
title: tval('Code'),
type: 'string',
'x-component': 'CodeMirror',
default:
'// ctx.query can get user query\n// ctx.body to pass your data to workflow or to client who trigger this webhook.',
} as ISchema,
},
],
};

View File

@ -1,13 +1,28 @@
import { Plugin } from '@tachybase/client';
import React from 'react';
import { Icon, Plugin } from '@tachybase/client';
import { tval } from '../../locale';
import { WebhookManager } from './WebhookManager';
const DispatcherSvg = () => (
<svg viewBox="0 0 1024 1024" width="1em" height="1em">
<path
d="M979.689326 108.982857H356.219611A144.530286 144.530286 0 0 0 217.028754 0c-64.804571 0.731429-121.124571 45.348571-137.654857 108.982857H44.045897a36.571429 36.571429 0 0 0-37.814857 15.798857 37.741714 37.741714 0 0 0 0 41.545143 36.571429 36.571429 0 0 0 37.814857 15.798857h35.328c15.945143 64.219429 72.411429 109.494857 137.654857 110.445715a144.530286 144.530286 0 0 0 139.117715-108.982858h623.542857a37.156571 37.156571 0 0 0 29.549714-36.571428 37.156571 37.156571 0 0 0-29.549714-36.571429v-1.462857zM144.909897 146.285714c0-40.374857 32.329143-73.142857 72.118857-73.142857s72.118857 32.768 72.118857 73.142857c0 19.382857-7.606857 38.034286-21.211428 51.712A71.533714 71.533714 0 0 1 217.101897 219.428571a72.630857 72.630857 0 0 1-72.118857-73.142857z m834.779429 694.125715H356.219611A144.530286 144.530286 0 0 0 217.028754 731.428571c-64.804571 0.731429-121.124571 45.348571-137.654857 108.982858H44.045897a36.571429 36.571429 0 0 0-37.814857 15.798857 37.741714 37.741714 0 0 0 0 41.545143 36.571429 36.571429 0 0 0 37.814857 15.798857h35.328c15.945143 64.219429 72.411429 109.494857 137.654857 110.445714a144.530286 144.530286 0 0 0 139.117715-108.982857h623.542857a36.571429 36.571429 0 0 0 37.814857-15.798857 37.741714 37.741714 0 0 0 0-41.545143 36.571429 36.571429 0 0 0-37.814857-15.798857v-1.462857zM144.909897 877.714286c0-40.374857 32.329143-73.142857 72.118857-73.142857s72.118857 32.768 72.118857 73.142857c0 19.382857-7.606857 38.034286-21.211428 51.712a71.533714 71.533714 0 0 1-50.907429 21.430857 72.630857 72.630857 0 0 1-72.118857-73.142857zM43.972754 474.697143h623.616A144.530286 144.530286 0 0 1 806.706469 365.714286c64.804571 0.731429 121.124571 45.348571 137.654857 108.982857h34.596571a36.571429 36.571429 0 0 1 37.814857 15.798857 37.741714 37.741714 0 0 1 0 41.545143 36.571429 36.571429 0 0 1-37.814857 15.798857h-34.596571A144.603429 144.603429 0 0 1 806.779611 658.285714a144.530286 144.530286 0 0 1-139.117714-108.982857H44.045897a36.571429 36.571429 0 0 1-37.814857-15.798857 37.741714 37.741714 0 0 1 0-41.545143 36.571429 36.571429 0 0 1 37.814857-15.798857v-1.462857zM807.803611 585.142857a73.142857 73.142857 0 1 0 0-146.285714 73.142857 73.142857 0 0 0 0 146.285714z"
fill="#4A4A4A"
opacity=".65"
></path>
</svg>
);
const Dispatcher = (props) => <Icon component={DispatcherSvg} {...props} />;
Icon.register({ Dispatcher });
export class PluginWebhook extends Plugin {
async load() {
this.app.pluginSettingsManager.add('webhook', {
title: tval('Webhook manager'),
icon: 'ShareAltOutlined',
this.app.pluginSettingsManager.add('workflow.webhook', {
title: tval('Dispatcher'),
icon: 'Dispatcher',
Component: WebhookManager,
});
}

View File

@ -123,6 +123,7 @@
"Delete record": "删除数据",
"Delete records of a collection. Could use variables in workflow context as filter. All records match the filter will be deleted.": "删除一个数据表中的数据。可以使用上游节点里的变量作为过滤条件。所有满足条件的数据都将被删除。",
"Disabled": "已失效",
"Dispatcher": "调度器",
"Distinct": "去重",
"Draft": "草稿",
"Duplicate to new workflow": "复制为新工作流",

View File

@ -12,8 +12,9 @@ export class PluginWebhook extends Plugin {
name: 'webhooks',
actions: {
trigger: new WebhookController().getLink,
test: new WebhookController().test,
},
});
this.app.acl.allow('webhooks', 'trigger', 'loggedIn');
this.app.acl.allow('webhooks', ['trigger', 'test'], 'loggedIn');
}
}

View File

@ -11,6 +11,7 @@ export class WebhookController {
if (name) {
where['filter'] = {
name: name,
type: 'code',
enabled: true,
};
}
@ -52,6 +53,14 @@ export class WebhookController {
return;
}
}
async test(ctx: Context) {
const { name, params, body } = ctx.action.params.values;
ctx.request.query = params;
ctx.action.params = params || {};
ctx.action.params.name = name;
ctx.action.params.values = body;
await new WebhookController().getLink(ctx);
}
}
function run(jsCode: string, { ctx, lib }) {