mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
* feat(plugin-workflow): add form trigger * test(plugin-workflow): add test cases * fix(client): fix component warning * fix(plugin-workflow): fix context data for create and update * fix(plugin-workflow): allow to select any form type workflow in configuration * fix(client): fix tree component value * fix(client): fix value render in component * fix(plugin-workflow): fix context load * fix(client): fix type * fix(client): fix type * fix(plugin-workflow): fix params * fix(plugin-workflow): fix required * fix(plugin): fix context not matching collection error * fix(plugin-workflow): fix test cases * refactor(plugin-workflow): change trigger workflow action config to cascaded * fix(plugin-workflow): remove useless locale * fix(client): adjust locale * fix(client): remove useless locale
328 lines
10 KiB
TypeScript
328 lines
10 KiB
TypeScript
import { InfoOutlined } from '@ant-design/icons';
|
|
import { createForm } from '@formily/core';
|
|
import { ISchema, useForm } from '@formily/react';
|
|
import {
|
|
ActionContextProvider,
|
|
SchemaComponent,
|
|
SchemaInitializerItemOptions,
|
|
css,
|
|
cx,
|
|
useAPIClient,
|
|
useActionContext,
|
|
useCompile,
|
|
useResourceActionContext,
|
|
} from '@nocobase/client';
|
|
import { Registry } from '@nocobase/utils/client';
|
|
import { Alert, Button, Input, Tag, message } from 'antd';
|
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
import { useFlowContext } from '../FlowContext';
|
|
import { DrawerDescription } from '../components/DrawerDescription';
|
|
import { NAMESPACE, lang } from '../locale';
|
|
import useStyles from '../style';
|
|
import { VariableOptions } from '../variable';
|
|
import collection from './collection';
|
|
import formTrigger from './form';
|
|
import schedule from './schedule/';
|
|
|
|
function useUpdateConfigAction() {
|
|
const form = useForm();
|
|
const api = useAPIClient();
|
|
const { workflow } = useFlowContext() ?? {};
|
|
const ctx = useActionContext();
|
|
const { refresh } = useResourceActionContext();
|
|
return {
|
|
async run() {
|
|
if (workflow.executed) {
|
|
message.error(lang('Trigger in executed workflow cannot be modified'));
|
|
return;
|
|
}
|
|
await form.submit();
|
|
await api.resource('workflows').update?.({
|
|
filterByTk: workflow.id,
|
|
values: {
|
|
config: form.values,
|
|
},
|
|
});
|
|
ctx.setVisible(false);
|
|
refresh();
|
|
},
|
|
};
|
|
}
|
|
|
|
export interface Trigger {
|
|
title: string;
|
|
type: string;
|
|
description?: string;
|
|
// group: string;
|
|
useVariables?(config: any, options?): VariableOptions;
|
|
fieldset: { [key: string]: ISchema };
|
|
view?: ISchema;
|
|
scope?: { [key: string]: any };
|
|
components?: { [key: string]: any };
|
|
useInitializers?(config): SchemaInitializerItemOptions | null;
|
|
initializers?: any;
|
|
}
|
|
|
|
export const triggers = new Registry<Trigger>();
|
|
|
|
triggers.register(formTrigger.type, formTrigger);
|
|
triggers.register(collection.type, collection);
|
|
triggers.register(schedule.type, schedule);
|
|
|
|
function TriggerExecution() {
|
|
const compile = useCompile();
|
|
const { workflow, execution } = useFlowContext();
|
|
const { styles } = useStyles();
|
|
|
|
if (!execution) {
|
|
return null;
|
|
}
|
|
|
|
const trigger = triggers.get(workflow.type);
|
|
|
|
return (
|
|
<SchemaComponent
|
|
schema={{
|
|
type: 'void',
|
|
name: 'execution',
|
|
'x-component': 'Action',
|
|
'x-component-props': {
|
|
title: <InfoOutlined />,
|
|
shape: 'circle',
|
|
size: 'small',
|
|
className: styles.nodeJobButtonClass,
|
|
type: 'primary',
|
|
},
|
|
properties: {
|
|
[execution.id]: {
|
|
type: 'void',
|
|
'x-decorator': 'Form',
|
|
'x-decorator-props': {
|
|
initialValue: execution,
|
|
},
|
|
'x-component': 'Action.Modal',
|
|
title: (
|
|
<div className={cx(styles.nodeTitleClass)}>
|
|
<Tag>{compile(trigger.title)}</Tag>
|
|
<strong>{workflow.title}</strong>
|
|
<span className="workflow-node-id">#{execution.id}</span>
|
|
</div>
|
|
),
|
|
properties: {
|
|
createdAt: {
|
|
type: 'string',
|
|
title: `{{t("Triggered at", { ns: "${NAMESPACE}" })}}`,
|
|
'x-decorator': 'FormItem',
|
|
'x-component': 'DatePicker',
|
|
'x-component-props': {
|
|
showTime: true,
|
|
},
|
|
'x-read-pretty': true,
|
|
},
|
|
context: {
|
|
type: 'object',
|
|
title: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`,
|
|
'x-decorator': 'FormItem',
|
|
'x-component': 'Input.JSON',
|
|
'x-component-props': {
|
|
className: css`
|
|
padding: 1em;
|
|
background-color: #eee;
|
|
`,
|
|
},
|
|
'x-read-pretty': true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export const TriggerConfig = () => {
|
|
const api = useAPIClient();
|
|
const compile = useCompile();
|
|
const { workflow, refresh } = useFlowContext();
|
|
const [editingTitle, setEditingTitle] = useState<string>('');
|
|
const [editingConfig, setEditingConfig] = useState(false);
|
|
const { styles } = useStyles();
|
|
let typeTitle = '';
|
|
useEffect(() => {
|
|
if (workflow) {
|
|
setEditingTitle(workflow.title ?? typeTitle);
|
|
}
|
|
}, [workflow]);
|
|
|
|
const form = useMemo(
|
|
() =>
|
|
createForm({
|
|
initialValues: workflow?.config,
|
|
values: workflow?.config,
|
|
disabled: workflow?.executed,
|
|
}),
|
|
[workflow],
|
|
);
|
|
|
|
if (!workflow || !workflow.type) {
|
|
return null;
|
|
}
|
|
const { title, type, executed } = workflow;
|
|
const trigger = triggers.get(type);
|
|
const { fieldset, scope, components } = trigger;
|
|
typeTitle = trigger.title;
|
|
const detailText = executed ? '{{t("View")}}' : '{{t("Configure")}}';
|
|
const titleText = lang('Trigger');
|
|
|
|
async function onChangeTitle(next) {
|
|
const t = next || typeTitle;
|
|
setEditingTitle(t);
|
|
if (t === title) {
|
|
return;
|
|
}
|
|
await api.resource('workflows').update?.({
|
|
filterByTk: workflow.id,
|
|
values: {
|
|
title: t,
|
|
},
|
|
});
|
|
refresh();
|
|
}
|
|
|
|
function onOpenDrawer(ev) {
|
|
if (ev.target === ev.currentTarget) {
|
|
setEditingConfig(true);
|
|
return;
|
|
}
|
|
const whiteSet = new Set(['workflow-node-meta', 'workflow-node-config-button', 'ant-input-disabled']);
|
|
for (let el = ev.target; el && el !== ev.currentTarget; el = el.parentNode) {
|
|
if ((Array.from(el.classList ?? []) as string[]).some((name: string) => whiteSet.has(name))) {
|
|
setEditingConfig(true);
|
|
ev.stopPropagation();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={cx(styles.nodeCardClass)} onClick={onOpenDrawer}>
|
|
<div className={cx(styles.nodeMetaClass, 'workflow-node-meta')}>
|
|
<Tag color="gold">{titleText}</Tag>
|
|
</div>
|
|
<div>
|
|
<Input.TextArea
|
|
value={editingTitle}
|
|
onChange={(ev) => setEditingTitle(ev.target.value)}
|
|
onBlur={(ev) => onChangeTitle(ev.target.value)}
|
|
autoSize
|
|
/>
|
|
</div>
|
|
<TriggerExecution />
|
|
<ActionContextProvider value={{ visible: editingConfig, setVisible: setEditingConfig }}>
|
|
<SchemaComponent
|
|
schema={{
|
|
name: `workflow-trigger-${workflow.id}`,
|
|
type: 'void',
|
|
properties: {
|
|
config: {
|
|
type: 'void',
|
|
'x-content': detailText,
|
|
'x-component': Button,
|
|
'x-component-props': {
|
|
type: 'link',
|
|
className: 'workflow-node-config-button',
|
|
},
|
|
},
|
|
drawer: {
|
|
type: 'void',
|
|
title: titleText,
|
|
'x-component': 'Action.Drawer',
|
|
'x-decorator': 'FormV2',
|
|
'x-decorator-props': {
|
|
form,
|
|
},
|
|
properties: {
|
|
...(executed
|
|
? {
|
|
alert: {
|
|
'x-component': Alert,
|
|
'x-component-props': {
|
|
type: 'warning',
|
|
showIcon: true,
|
|
message: `{{t("Trigger in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
|
|
className: css`
|
|
width: 100%;
|
|
font-size: 85%;
|
|
margin-bottom: 2em;
|
|
`,
|
|
},
|
|
},
|
|
}
|
|
: trigger.description
|
|
? {
|
|
description: {
|
|
type: 'void',
|
|
'x-component': DrawerDescription,
|
|
'x-component-props': {
|
|
label: lang('Trigger type'),
|
|
title: trigger.title,
|
|
description: trigger.description,
|
|
},
|
|
},
|
|
}
|
|
: {}),
|
|
fieldset: {
|
|
type: 'void',
|
|
'x-component': 'fieldset',
|
|
'x-component-props': {
|
|
className: css`
|
|
.ant-select.auto-width {
|
|
width: auto;
|
|
min-width: 6em;
|
|
}
|
|
`,
|
|
},
|
|
properties: fieldset,
|
|
},
|
|
actions: {
|
|
...(executed
|
|
? {}
|
|
: {
|
|
type: 'void',
|
|
'x-component': 'Action.Drawer.Footer',
|
|
properties: {
|
|
cancel: {
|
|
title: '{{t("Cancel")}}',
|
|
'x-component': 'Action',
|
|
'x-component-props': {
|
|
useAction: '{{ cm.useCancelAction }}',
|
|
},
|
|
},
|
|
submit: {
|
|
title: '{{t("Submit")}}',
|
|
'x-component': 'Action',
|
|
'x-component-props': {
|
|
type: 'primary',
|
|
useAction: useUpdateConfigAction,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
scope={scope}
|
|
components={components}
|
|
/>
|
|
</ActionContextProvider>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export function useTrigger() {
|
|
const { workflow } = useFlowContext();
|
|
return triggers.get(workflow.type);
|
|
}
|