feat: support workflow bulk (#858)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#858
This commit is contained in:
parent
fda9853509
commit
46129499be
@ -53,5 +53,6 @@ export * from './variable';
|
||||
export * from './custom-cascader';
|
||||
export * from './unixTimestamp';
|
||||
export * from './nanoIDInput';
|
||||
export { PageHeader } from '@ant-design/pro-layout';
|
||||
|
||||
import './index.less';
|
||||
|
@ -30,7 +30,7 @@ const approvals = {
|
||||
const workflow = await context.db.getRepository('workflows').findOne({
|
||||
filterByTk: workflowId,
|
||||
});
|
||||
if (!(workflow == null ? void 0 : workflow.enabled)) {
|
||||
if (!workflow?.enabled) {
|
||||
return context.throw(400, 'Current workflow not found or disabled, please refresh and try again');
|
||||
}
|
||||
if (status !== APPROVAL_STATUS.DRAFT) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "@hera/plugin-core",
|
||||
"displayName": "Hera platform",
|
||||
"version": "1.7.28",
|
||||
"description": "Hera platform as nocobase plugin.",
|
||||
"description": "Hera platform",
|
||||
"keywords": [
|
||||
"System management"
|
||||
],
|
||||
@ -11,13 +11,13 @@
|
||||
"@ant-design/cssinjs": "^1.11.1",
|
||||
"@ant-design/icons": "~5.3.6",
|
||||
"@ant-design/pro-layout": "^7.16.11",
|
||||
"@emotion/css": "^11.11.2",
|
||||
"@formily/antd-v5": "1.x",
|
||||
"@react-pdf/renderer": "^3.3.2",
|
||||
"@tachybase/components": "workspace:*",
|
||||
"@tachybase/schema": "workspace:*",
|
||||
"@types/lodash": "~4.17.0",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.16.1",
|
||||
"antd-style": "3.4.5",
|
||||
"classnames": "^2.3.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"dayjs": "^1.11.8",
|
||||
@ -50,7 +50,9 @@
|
||||
"@nocobase/plugin-acl": "workspace:*",
|
||||
"@nocobase/server": "workspace:*",
|
||||
"@nocobase/test": "workspace:*",
|
||||
"@nocobase/utils": "workspace:*"
|
||||
"@nocobase/utils": "workspace:*",
|
||||
"@tachybase/plugin-workflow": "workspace:*",
|
||||
"@tachybase/schema": "workspace:*"
|
||||
},
|
||||
"description.zh-CN": "提供标准赫拉平台能力",
|
||||
"displayName.zh-CN": "赫拉平台"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css } from '@nocobase/client';
|
||||
import { ArrayCollapse } from '@tachybase/components';
|
||||
import { Form } from '@tachybase/schema';
|
||||
import { observer, useField, useFieldSchema } from '@tachybase/schema';
|
||||
|
@ -1,39 +1,12 @@
|
||||
import React from 'react';
|
||||
import { ActionInitializer, Application } from '@nocobase/client';
|
||||
import { ActionInitializer } from '@nocobase/client';
|
||||
import { Popover, Space, Button, Input } from 'antd';
|
||||
import { useFieldSchema } from '@tachybase/schema';
|
||||
import { useProps } from '@nocobase/client';
|
||||
import { ShareAltOutlined } from '@ant-design/icons';
|
||||
import { lang, tval, useTranslation } from '../../locale';
|
||||
import { useOutboundActionProps } from './hooks/useOutboundActionProps';
|
||||
import { tval, useTranslation } from '../../locale';
|
||||
|
||||
export class OutboundActionHelper {
|
||||
constructor(private app: Application) {}
|
||||
|
||||
async load() {
|
||||
this.app.addScopes({
|
||||
useOutboundActionProps,
|
||||
});
|
||||
this.app.addComponents({
|
||||
OutboundButton,
|
||||
OutboundLinkActionInitializer,
|
||||
});
|
||||
const outboundItem = {
|
||||
type: 'item',
|
||||
name: 'enableActions.outbound',
|
||||
title: lang('outbound'),
|
||||
Component: 'OutboundLinkActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
},
|
||||
};
|
||||
this.app.schemaInitializerManager.addItem('table:configureActions', outboundItem.name, outboundItem);
|
||||
this.app.schemaInitializerManager.addItem('details:configureActions', outboundItem.name, outboundItem);
|
||||
this.app.schemaInitializerManager.addItem('kanban:configureActions', outboundItem.name, outboundItem);
|
||||
}
|
||||
}
|
||||
|
||||
export const OutboundLinkActionInitializer: React.FC = (props) => {
|
||||
export const OutboundLinkActionInitializer = (props) => {
|
||||
const schema = {
|
||||
title: tval('outbound'),
|
||||
'x-action': 'outbound',
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { AdminProvider, RemoteCollectionManagerProvider, RemoteSchemaComponent } from '@nocobase/client';
|
||||
import { useParams } from 'react-router-dom';
|
||||
export const OutboundPage: React.FC = () => {
|
||||
export const OutboundPage = () => {
|
||||
const params = useParams();
|
||||
return (
|
||||
<AdminProvider>
|
@ -0,0 +1,33 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { OutboundPage } from './OutboundPage';
|
||||
import { OutboundButton, OutboundLinkActionInitializer } from './OutboundLinkActionInitializer';
|
||||
import { lang } from '../../locale';
|
||||
import { useOutboundActionProps } from './useOutboundActionProps';
|
||||
|
||||
export class PluginOutbound extends Plugin {
|
||||
async load() {
|
||||
this.app.router.add('outbound', {
|
||||
path: '/r/:id',
|
||||
Component: OutboundPage,
|
||||
});
|
||||
this.app.addScopes({
|
||||
useOutboundActionProps,
|
||||
});
|
||||
this.app.addComponents({
|
||||
OutboundButton,
|
||||
OutboundLinkActionInitializer,
|
||||
});
|
||||
const outboundItem = {
|
||||
type: 'item',
|
||||
name: 'enableActions.outbound',
|
||||
title: lang('outbound'),
|
||||
Component: 'OutboundLinkActionInitializer',
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
},
|
||||
};
|
||||
this.app.schemaInitializerManager.addItem('table:configureActions', outboundItem.name, outboundItem);
|
||||
this.app.schemaInitializerManager.addItem('details:configureActions', outboundItem.name, outboundItem);
|
||||
this.app.schemaInitializerManager.addItem('kanban:configureActions', outboundItem.name, outboundItem);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { message } from 'antd';
|
||||
import { useFieldSchema } from '@tachybase/schema';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useTranslation } from '../../../locale';
|
||||
import { useTranslation } from '../../locale';
|
||||
|
||||
export const useOutboundActionProps = () => {
|
||||
let schema = useFieldSchema();
|
@ -0,0 +1,142 @@
|
||||
import { useCompile, useCollectionManager_deprecated, useCollectionDataSource } from '@nocobase/client';
|
||||
import { lang, tval } from '../../locale';
|
||||
|
||||
import {
|
||||
Trigger,
|
||||
CollectionBlockInitializer,
|
||||
getCollectionFieldOptions,
|
||||
FieldsSelect,
|
||||
RadioWithTooltip,
|
||||
CheckboxGroupWithTooltip,
|
||||
} from '@tachybase/plugin-workflow/client';
|
||||
|
||||
const enum ACTION_TYPES {
|
||||
CREATE = 'create',
|
||||
UPDATE = 'update',
|
||||
UPSERT = 'updateOrCreate',
|
||||
DESTROY = 'destroy',
|
||||
}
|
||||
|
||||
function useItems(item, options) {
|
||||
const compile = useCompile();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
return [
|
||||
{ label: lang('ID'), value: 'filterByTk' },
|
||||
...(item.action !== ACTION_TYPES.DESTROY
|
||||
? [
|
||||
{
|
||||
label: lang('Values submitted'),
|
||||
value: 'values',
|
||||
children: getCollectionFieldOptions({
|
||||
...options,
|
||||
appends: null,
|
||||
depth: 3,
|
||||
collection: item.collection,
|
||||
compile,
|
||||
getCollectionFields,
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
export class ApiTrigger extends Trigger {
|
||||
title = lang('API event');
|
||||
description = lang('api trigger');
|
||||
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 = (a, u) => {
|
||||
const { global: m } = a;
|
||||
return !m && !u.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();
|
||||
const result = getCollectionFieldOptions({
|
||||
appends: ['user'],
|
||||
...options,
|
||||
fields: [
|
||||
{
|
||||
collectionName: 'users',
|
||||
name: 'user',
|
||||
type: 'hasOne',
|
||||
target: 'users',
|
||||
uiSchema: { title: tval('User acted') },
|
||||
},
|
||||
],
|
||||
compile,
|
||||
getCollectionFields,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const parametersSchema = [{ label: lang('Parameters'), value: 'params', children: useItems(config, options) }];
|
||||
return [...result, { label: lang('Role of user acted'), value: 'roleName' }, ...parametersSchema];
|
||||
}
|
||||
useInitializers(item) {
|
||||
return item.collection
|
||||
? {
|
||||
name: 'triggerData',
|
||||
type: 'item',
|
||||
key: 'triggerData',
|
||||
title: tval('Trigger data'),
|
||||
Component: CollectionBlockInitializer,
|
||||
collection: item.collection,
|
||||
dataPath: '$context.params.values',
|
||||
}
|
||||
: null;
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
import {
|
||||
ActionDesigner,
|
||||
SchemaSettings,
|
||||
SchemaSettingsItemType,
|
||||
useDesignable,
|
||||
useRequest,
|
||||
useSchemaToolbar,
|
||||
} from '@nocobase/client';
|
||||
import { useFieldSchema } from '@tachybase/schema';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const schemaSettingsItems: SchemaSettingsItemType[] = [
|
||||
{
|
||||
name: 'editButton',
|
||||
Component: ActionDesigner.ButtonEditor,
|
||||
useComponentProps() {
|
||||
const { buttonEditorProps } = useSchemaToolbar();
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
type: 'select',
|
||||
useComponentProps() {
|
||||
const { dn } = useDesignable();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return {
|
||||
title: t('Data will be updated'),
|
||||
options: [
|
||||
{ label: t('Selected'), value: 'selected' },
|
||||
{ label: t('All'), value: 'all' },
|
||||
],
|
||||
value: fieldSchema['x-action-settings']?.['updateMode'],
|
||||
onChange: (value) => {
|
||||
fieldSchema['x-action-settings']['updateMode'] = value;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
'x-action-settings': fieldSchema['x-action-settings'],
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
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: 'api',
|
||||
},
|
||||
},
|
||||
});
|
||||
// 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,
|
||||
Component: ActionDesigner.RemoveButton as any,
|
||||
useComponentProps() {
|
||||
const { removeButtonProps } = useSchemaToolbar();
|
||||
return removeButtonProps;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const bulkWorkflowActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:bulkWorkflow',
|
||||
items: schemaSettingsItems,
|
||||
});
|
||||
|
||||
export { bulkWorkflowActionSettings };
|
@ -0,0 +1,30 @@
|
||||
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { tval } from '../../locale';
|
||||
|
||||
export const BulkWorkflowActionInitializer = () => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const schema = {
|
||||
type: 'void',
|
||||
title: tval('Bulk workflow'),
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCustomizeBulkWorkflowActionProps',
|
||||
'x-align': 'right',
|
||||
'x-acl-action': 'update',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
'x-action': 'customize:bulkWorkflow',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:bulkWorkflow',
|
||||
'x-action-settings': {
|
||||
bindWorkflow: false,
|
||||
updateMode: 'selected',
|
||||
},
|
||||
'x-component-props': {
|
||||
icon: 'CarryOutOutlined',
|
||||
},
|
||||
};
|
||||
return <BlockInitializer {...itemConfig} schema={schema} item={itemConfig} />;
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
import { BlockInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
export const CustomizeActionInitializer = () => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
return <BlockInitializer {...itemConfig} item={itemConfig} />;
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import { Plugin, useCollection } from '@nocobase/client';
|
||||
import { tval } from '../../locale';
|
||||
import { BulkWorkflowActionInitializer } from './BulkUpdateActionInitializer';
|
||||
import { bulkWorkflowActionSettings } from './BulkUpdateAction.Settings';
|
||||
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
|
||||
import { useCustomizeBulkWorkflowActionProps } from './utils';
|
||||
import PluginWorkflow from '@tachybase/plugin-workflow/client';
|
||||
import { ApiTrigger } from './ApiTrigger';
|
||||
|
||||
export class PluginWorkflowBulk extends Plugin {
|
||||
async load() {
|
||||
this.app.addComponents({ CustomizeActionInitializer });
|
||||
this.app.addScopes({ useCustomizeBulkWorkflowActionProps });
|
||||
this.app.schemaSettingsManager.add(bulkWorkflowActionSettings);
|
||||
|
||||
const initializerData = {
|
||||
title: tval('Bulk workflow'),
|
||||
Component: BulkWorkflowActionInitializer,
|
||||
name: 'bulkWorkflow',
|
||||
useVisible() {
|
||||
const collection = useCollection();
|
||||
return (
|
||||
(collection.template !== 'view' || collection?.writableView) &&
|
||||
collection.template !== 'file' &&
|
||||
collection.template !== 'sql'
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
['table', 'details'].forEach((block) => {
|
||||
this.app.schemaInitializerManager.addItem(block + ':configureActions', 'customize.bulkWorkflow', initializerData);
|
||||
});
|
||||
|
||||
const plugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
|
||||
plugin.registerTrigger('api', ApiTrigger);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
|
||||
import {
|
||||
isVariable,
|
||||
transformVariableValue,
|
||||
useBlockRequestContext,
|
||||
useCollection_deprecated,
|
||||
useCompile,
|
||||
useLocalVariables,
|
||||
useRecord,
|
||||
useRequest,
|
||||
useTableBlockContext,
|
||||
useVariables,
|
||||
} from '@nocobase/client';
|
||||
import { App } from 'antd';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from '../../locale';
|
||||
|
||||
export const useCustomizeBulkWorkflowActionProps = () => {
|
||||
const { field, resource, __parent, service } = useBlockRequestContext();
|
||||
const expressionScope = useContext(SchemaExpressionScopeContext);
|
||||
const actionSchema = useFieldSchema();
|
||||
const tableBlockContext = useTableBlockContext();
|
||||
const { rowKey } = tableBlockContext;
|
||||
const selectedRecordKeys =
|
||||
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
||||
const navigate = useNavigate();
|
||||
const compile = useCompile();
|
||||
const { t } = useTranslation();
|
||||
const actionField: any = useField();
|
||||
const { modal } = App.useApp();
|
||||
const variables = useVariables();
|
||||
const record = useRecord();
|
||||
const { name, getField } = useCollection_deprecated();
|
||||
const localVariables = useLocalVariables();
|
||||
const { run } = useRequest(
|
||||
{
|
||||
resource: 'workflows',
|
||||
action: 'trigger',
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
async onClick() {
|
||||
const {
|
||||
bindWorkflow = false,
|
||||
assignedValues: originalAssignedValues = {},
|
||||
updateMode,
|
||||
} = actionSchema?.['x-action-settings'] ?? {};
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
|
||||
if (!bindWorkflow) {
|
||||
return modal.info({
|
||||
title: t('Not bind workflow!'),
|
||||
});
|
||||
}
|
||||
|
||||
const assignedValues = {};
|
||||
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
|
||||
const value = originalAssignedValues[key];
|
||||
const collectionField = getField(key);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!collectionField) {
|
||||
throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
}
|
||||
});
|
||||
await Promise.all(waitList);
|
||||
|
||||
modal.confirm({
|
||||
title: t('Bulk update', { ns: 'client' }),
|
||||
content:
|
||||
updateMode === 'selected'
|
||||
? t('Update selected data?', { ns: 'client' })
|
||||
: t('Update all data?', { ns: 'client' }),
|
||||
async onOk() {
|
||||
run({
|
||||
filterByTk: bindWorkflow,
|
||||
});
|
||||
actionField.data.loading = false;
|
||||
},
|
||||
async onCancel() {
|
||||
actionField.data.loading = false;
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -0,0 +1,145 @@
|
||||
import { useCompile, useCollectionManager_deprecated, useCollectionDataSource } from '@nocobase/client';
|
||||
import { lang, tval } from '../../locale';
|
||||
|
||||
import {
|
||||
Trigger,
|
||||
CollectionBlockInitializer,
|
||||
getCollectionFieldOptions,
|
||||
FieldsSelect,
|
||||
RadioWithTooltip,
|
||||
CheckboxGroupWithTooltip,
|
||||
} from '@tachybase/plugin-workflow/client';
|
||||
|
||||
const enum ACTION_TYPES {
|
||||
CREATE = 'create',
|
||||
UPDATE = 'update',
|
||||
UPSERT = 'updateOrCreate',
|
||||
DESTROY = 'destroy',
|
||||
}
|
||||
|
||||
function useItems(item, options) {
|
||||
const compile = useCompile();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
return [
|
||||
{ label: lang('ID'), value: 'filterByTk' },
|
||||
...(item.action !== ACTION_TYPES.DESTROY
|
||||
? [
|
||||
{
|
||||
label: lang('Values submitted'),
|
||||
value: 'values',
|
||||
children: getCollectionFieldOptions({
|
||||
...options,
|
||||
appends: null,
|
||||
depth: 3,
|
||||
collection: item.collection,
|
||||
compile,
|
||||
getCollectionFields,
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
export class WorkflowTriggerInterceptor extends Trigger {
|
||||
sync = true;
|
||||
title = lang('Pre-action event');
|
||||
description = lang(
|
||||
'Triggered before the execution of a request initiated through an action button or API, such as before adding, updating, or deleting data. Suitable for data validation and logic judgment before action, and the request could be rejected by using the "End process" node.',
|
||||
);
|
||||
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 = (a, u) => {
|
||||
const { global: m } = a;
|
||||
return !m && !u.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();
|
||||
const result = getCollectionFieldOptions({
|
||||
appends: ['user'],
|
||||
...options,
|
||||
fields: [
|
||||
{
|
||||
collectionName: 'users',
|
||||
name: 'user',
|
||||
type: 'hasOne',
|
||||
target: 'users',
|
||||
uiSchema: { title: tval('User acted') },
|
||||
},
|
||||
],
|
||||
compile,
|
||||
getCollectionFields,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const parametersSchema = [{ label: lang('Parameters'), value: 'params', children: useItems(config, options) }];
|
||||
return [...result, { label: lang('Role of user acted'), value: 'roleName' }, ...parametersSchema];
|
||||
}
|
||||
useInitializers(item) {
|
||||
return item.collection
|
||||
? {
|
||||
name: 'triggerData',
|
||||
type: 'item',
|
||||
key: 'triggerData',
|
||||
title: tval('Trigger data'),
|
||||
Component: CollectionBlockInitializer,
|
||||
collection: item.collection,
|
||||
dataPath: '$context.params.values',
|
||||
}
|
||||
: null;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import PluginWorkflow from '@tachybase/plugin-workflow/client';
|
||||
import { WorkflowTriggerInterceptor } from './WorkflowTriggerInterceptor';
|
||||
|
||||
export class PluginWorkflowInterceptor extends Plugin {
|
||||
async load() {
|
||||
const plugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
|
||||
plugin.registerTrigger('request-interception', WorkflowTriggerInterceptor);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Plugin, RemoteSchemaTemplateManagerProvider, EditTitleField, useCollection } from '@nocobase/client';
|
||||
import { remove } from 'lodash';
|
||||
import { Plugin, EditTitleField } from '@nocobase/client';
|
||||
import { useFieldSchema } from '@tachybase/schema';
|
||||
import { isValid } from '@tachybase/schema';
|
||||
import { autorun } from '@tachybase/schema';
|
||||
@ -16,7 +15,7 @@ import {
|
||||
import { useCreateActionProps } from './schema-initializer/actions/hooks/useCreateActionProps';
|
||||
import { useGetCustomAssociatedComponents } from './hooks/useGetCustomAssociatedComponents';
|
||||
import { useGetCustomComponents } from './hooks/useGetCustomComponents';
|
||||
import { AdminLayout, DetailsPage, HomePage, OutboundPage, PageLayout } from './pages';
|
||||
import { AdminLayout, DetailsPage, HomePage, PageLayout } from './pages';
|
||||
import { PluginSettingsHelper } from './settings-manager-components';
|
||||
import {
|
||||
AssociatedFieldInterface,
|
||||
@ -39,12 +38,7 @@ import {
|
||||
ExcelFile,
|
||||
} from './components';
|
||||
import { AutoComplete } from './schema-components';
|
||||
import {
|
||||
CreateSubmitActionInitializer,
|
||||
GroupBlockPlugin,
|
||||
OutboundActionHelper,
|
||||
SettingBlockInitializer,
|
||||
} from './schema-initializer';
|
||||
import { CreateSubmitActionInitializer, GroupBlockPlugin, SettingBlockInitializer } from './schema-initializer';
|
||||
import {
|
||||
SheetBlock,
|
||||
SheetBlockInitializer,
|
||||
@ -67,8 +61,11 @@ import { PluginHeraVersion } from './features/hera-version';
|
||||
import { PluginAssistant } from './features/assistant';
|
||||
import { TstzrangeFieldInterface } from './interfaces/TstzrangeFieldInterface';
|
||||
import { PluginContextMenu } from './features/context-menu';
|
||||
import { PluginWorkflowBulk } from './features/workflow-bulk';
|
||||
import { PluginWorkflowInterceptor } from './features/workflow-interceptor';
|
||||
import { PluginPDF } from './features/pdf';
|
||||
import { PluginExtendedFilterForm } from './features/extended-filter-form';
|
||||
import { PluginOutbound } from './features/outbound';
|
||||
export { usePDFViewerRef } from './schema-initializer';
|
||||
export * from './components/custom-components/custom-components';
|
||||
|
||||
@ -83,8 +80,11 @@ export class PluginCoreClient extends Plugin {
|
||||
await this.app.pm.add(PluginHeraVersion);
|
||||
await this.app.pm.add(PluginContextMenu);
|
||||
await this.app.pm.add(PluginAssistant);
|
||||
await this.app.pm.add(PluginWorkflowBulk);
|
||||
await this.app.pm.add(PluginWorkflowInterceptor);
|
||||
await this.app.pm.add(PluginPDF);
|
||||
await this.app.pm.add(PluginExtendedFilterForm);
|
||||
await this.app.pm.add(PluginOutbound);
|
||||
}
|
||||
|
||||
async registerSettings() {
|
||||
@ -162,10 +162,6 @@ export class PluginCoreClient extends Plugin {
|
||||
}
|
||||
|
||||
async registerRouters() {
|
||||
this.app.router.add('outbound', {
|
||||
path: '/r/:id',
|
||||
element: <OutboundPage />,
|
||||
});
|
||||
this.app.router.remove('root');
|
||||
this.app.router.add('home', {
|
||||
path: '/',
|
||||
@ -242,7 +238,6 @@ export class PluginCoreClient extends Plugin {
|
||||
|
||||
async load() {
|
||||
this.locale = new Locale(this.app);
|
||||
await new OutboundActionHelper(this.app).load();
|
||||
await new PluginSettingsHelper(this.app).load();
|
||||
await this.registerScopesAndComponents();
|
||||
await this.registerSettings();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css } from '@nocobase/client';
|
||||
import { useSessionStorageState } from 'ahooks';
|
||||
import { App, Layout } from 'antd';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
@ -1,15 +1,8 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
CollectionRecordProvider,
|
||||
DataBlockProvider,
|
||||
RecordProvider,
|
||||
RemoteSchemaComponent,
|
||||
css,
|
||||
} from '@nocobase/client';
|
||||
import { DataBlockProvider, RecordProvider, RemoteSchemaComponent, css, PageHeader } from '@nocobase/client';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import { useHeadStyles } from './style';
|
||||
import { PageHeader as AntdPageHeader } from '@ant-design/pro-layout';
|
||||
|
||||
export const DetailsPage: React.FC = () => {
|
||||
const params = useParams<any>();
|
||||
@ -32,7 +25,7 @@ export const DetailsPage: React.FC = () => {
|
||||
`}
|
||||
>
|
||||
<div className={`${styles['.pageHeaderCss']}`}>
|
||||
<AntdPageHeader
|
||||
<PageHeader
|
||||
ghost={false}
|
||||
title={'详情页面'}
|
||||
onBack={() => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export * from './AdminLayout';
|
||||
export * from './DetailsPage';
|
||||
export * from './Home';
|
||||
export * from './OutboundPage';
|
||||
export * from '../features/outbound/OutboundPage';
|
||||
export * from './PageLayout';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createStyles } from 'antd-style';
|
||||
import { createStyles } from '@nocobase/client';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => ({
|
||||
home: css`
|
||||
|
@ -1,6 +1,6 @@
|
||||
export * from './items/CustomFilterFormItemInitializer';
|
||||
export * from './blocks/PDFVIewerBlockInitializer';
|
||||
export * from './actions/OutboundLinkActionInitializer';
|
||||
export * from '../features/outbound/OutboundLinkActionInitializer';
|
||||
export * from './actions/CreateSubmitActionInitializer';
|
||||
export * from './blocks/SettingBlockInitializer';
|
||||
export * from './blocks/GroupBlockInitializer';
|
||||
|
@ -15,7 +15,6 @@ import {
|
||||
useDesignable,
|
||||
useFormBlockContext,
|
||||
useFormBlockType,
|
||||
useLinkageCollectionFilterOptions,
|
||||
useLocalVariables,
|
||||
useRecord,
|
||||
useSchemaTemplateManager,
|
||||
@ -24,7 +23,7 @@ import {
|
||||
VariableScopeProvider,
|
||||
} from '@nocobase/client';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from '../locale';
|
||||
import { FormFilterScope } from '../components/filter-form/FormFilterScope';
|
||||
import { useFieldComponents } from '../schema-initializer';
|
||||
|
@ -12,10 +12,13 @@
|
||||
"Configure": "Configure",
|
||||
"Copy Failed": "Copy Failed",
|
||||
"Copy embedded link": "Copy embedded link",
|
||||
"Copy link": "Copy link",
|
||||
"Copy successful": "Copy successful",
|
||||
"Create a record": "Create a record",
|
||||
"Custom filter": "Custom filter",
|
||||
"Custom option label": "Custom option label",
|
||||
"Delete department": "Delete department",
|
||||
"Delete": "Delete",
|
||||
"Department name": "Department name",
|
||||
"Department": "Department",
|
||||
"Departments management": "Departments management",
|
||||
@ -37,6 +40,7 @@
|
||||
"Owners": "Owners",
|
||||
"Page style": "Page Style",
|
||||
"Pagination": "Pagination",
|
||||
"Parameters": "Parameters",
|
||||
"Pick a data entry for viewing and editing": "Pick a data entry for viewing and editing",
|
||||
"Please select departments": "Please select departments",
|
||||
"Please select members": "Please select members",
|
||||
@ -45,6 +49,9 @@
|
||||
"Remove departments": "Remove departments",
|
||||
"Remove member": "Remove member",
|
||||
"Remove members": "Remove members",
|
||||
"Request inteception": "Request inteception",
|
||||
"Request to intercept": "Request to intercept",
|
||||
"Role of user acted": "Role of user acted",
|
||||
"Roles management": "Roles management",
|
||||
"Search for departments, users": "Search for departments, users",
|
||||
"Search results": "Search results",
|
||||
@ -56,15 +63,23 @@
|
||||
"System setting": "System setting",
|
||||
"The department has members, please remove them first": "The department has members, please remove them first",
|
||||
"The department has sub-departments, please delete them first": "The department has sub-departments, please delete them first",
|
||||
"The following selected requests all trigger interception on submission": "The following selected requests all trigger interception on submission",
|
||||
"Third-party integration configuration": "Third-party integration configuration",
|
||||
"This field is currently not supported for use in form blocks.": "This field is currently not supported for use in form blocks.",
|
||||
"This month": "This month",
|
||||
"This year": "This year",
|
||||
"Trigger mode": "Trigger mode",
|
||||
"Triggered before submitting a create, update, or delete request to a collection, so that data validation, logical judgment, etc., can be performed before executing the request, and then either approving the request or rejecting the request by using the \"End process\" node.": "Triggered before submitting a create, update, or delete request to a collection, so that data validation, logical judgment, etc., can be performed before executing the request, and then either approving the request or rejecting the request by using the \"End process\" node.",
|
||||
"Triggered only when a form bound to this workflow is submitted": "Triggered only when a form bound to this workflow is submitted",
|
||||
"Update a record": "Update a record",
|
||||
"User acted": "User acted",
|
||||
"Values submitted": "Values submitted",
|
||||
"classical": "classical",
|
||||
"component": "component",
|
||||
"default": "default",
|
||||
"error": "error",
|
||||
"loading...": "loading...",
|
||||
"outbound": "outbound",
|
||||
"preview block": "preview block",
|
||||
"tabs": "tabs",
|
||||
"year": "year"
|
||||
|
@ -12,21 +12,26 @@
|
||||
"Configure": "配置",
|
||||
"Copy Failed": "复制失败",
|
||||
"Copy embedded link": "复制嵌入链接",
|
||||
"Copy link": "复制链接",
|
||||
"Copy successful": "复制成功",
|
||||
"Create record action": "创建记录操作",
|
||||
"Custom filter": "自定义筛选",
|
||||
"Custom option label": "自定义选项标签",
|
||||
"Delete department": "删除部门",
|
||||
"Delete record action": "删除记录操作",
|
||||
"Department name": "部门名称",
|
||||
"Department": "部门",
|
||||
"Departments management": "部门管理",
|
||||
"Departments": "部门",
|
||||
"Edit department": "编辑部门",
|
||||
"Global mode, triggered before executing the following actions": "全局模式,以下操作执行前都触发",
|
||||
"Group block": "汇总区块",
|
||||
"Group": "汇总",
|
||||
"Hera integration": "赫拉集成",
|
||||
"HomePage Config": "主页配置",
|
||||
"Last month": "上月",
|
||||
"Link manager": "链接管理器",
|
||||
"Local mode, triggered before executing the actions bound to this workflow": "局部模式,绑定该工作流的操作执行前触发",
|
||||
"Main department": "主属部门",
|
||||
"Main": "主属部门",
|
||||
"Mobile UI": "移动端页面",
|
||||
@ -37,17 +42,21 @@
|
||||
"Owners": "负责人",
|
||||
"Page style": "页面版本",
|
||||
"Pagination": "分页",
|
||||
"Parameters": "参数",
|
||||
"Pick a data entry for viewing and editing": "选择一条数据用于查看与编辑",
|
||||
"Please select departments": "请选择部门",
|
||||
"Please select members": "请选择成员",
|
||||
"Please select": "请选择",
|
||||
"Pre-action event": "操作前事件",
|
||||
"Remove department": "移除部门",
|
||||
"Remove departments": "移除部门",
|
||||
"Remove member": "移除成员",
|
||||
"Remove members": "移除成员",
|
||||
"Role of user acted": "操作者角色",
|
||||
"Roles management": "角色管理",
|
||||
"Search for departments, users": "搜索部门、用户",
|
||||
"Search results": "搜索结果",
|
||||
"Select actions": "选择操作",
|
||||
"Select submit data type": "增量提交",
|
||||
"Set as main department": "设置为主属部门",
|
||||
"Signature input": "手写签名",
|
||||
@ -60,11 +69,17 @@
|
||||
"This field is currently not supported for use in form blocks.": "该字段目前不支持在表单区块中使用。",
|
||||
"This month": "本月",
|
||||
"This year": "今年",
|
||||
"Trigger mode": "触发模式",
|
||||
"Triggered before the execution of a request initiated through an action button or API, such as before adding, updating, or deleting data. Suitable for data validation and logic judgment before action, and the request could be rejected by using the \"End process\" node.": "通过操作按钮或 API 发起请求并在执行前触发,比如新增、更新、删除数据之前。适用于在操作前进行数据验证、逻辑判断,并可通过“结束节点”来拦截请求。",
|
||||
"Update record action": "更新记录操作",
|
||||
"User acted": "操作者",
|
||||
"Values submitted": "提交的值对象",
|
||||
"classical": "经典版",
|
||||
"component": "组件",
|
||||
"default": "默认",
|
||||
"error": "出错了",
|
||||
"loading...": "加载中...",
|
||||
"outbound": "外链",
|
||||
"preview block": "预览区块",
|
||||
"tabs": "多标签",
|
||||
"year": "年"
|
||||
|
@ -0,0 +1,7 @@
|
||||
import { Trigger } from '@tachybase/plugin-workflow';
|
||||
|
||||
export class ApiTrigger extends Trigger {
|
||||
static TYPE = 'api';
|
||||
on() {}
|
||||
off() {}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
import PluginErrorHandler from '@nocobase/plugin-error-handler';
|
||||
import { Trigger, EXECUTION_STATUS } from '@tachybase/plugin-workflow';
|
||||
import { joinCollectionName } from '@nocobase/data-source-manager';
|
||||
import type { Context, Next } from '@nocobase/actions';
|
||||
import PluginWorkflow from '@tachybase/plugin-workflow';
|
||||
|
||||
class RequestInterceptionError extends Error {
|
||||
status = 400;
|
||||
messages = [];
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'RequestInterceptionError';
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestInterceptionTrigger extends Trigger {
|
||||
static TYPE = 'request-interception';
|
||||
sync = true;
|
||||
middleware = async (context: Context, next: Next) => {
|
||||
const {
|
||||
resourceName,
|
||||
actionName,
|
||||
params: { filterByTk, filter, values, triggerWorkflows = '' },
|
||||
} = context.action;
|
||||
const dataSourceHeader = context.get('x-data-source');
|
||||
const jointCollectionName = joinCollectionName(dataSourceHeader, resourceName);
|
||||
const triggerWorkflowsMap = new Map();
|
||||
const triggerWorkflowsArray = [];
|
||||
for (const trigger of triggerWorkflows.split(',')) {
|
||||
const [key, path] = trigger.split('!');
|
||||
triggerWorkflowsMap.set(key, path);
|
||||
triggerWorkflowsArray.push(key);
|
||||
}
|
||||
const workflows = Array.from(this.workflow.enabledCache.values()).filter(
|
||||
(item) => item.type === RequestInterceptionTrigger.TYPE && item.config.collection === jointCollectionName,
|
||||
);
|
||||
const globalWorkflows = workflows
|
||||
.filter((item) => {
|
||||
return item.config.global && item.config.actions?.includes(actionName);
|
||||
})
|
||||
.sort((a, b) => a.id - b.id);
|
||||
const localWorkflows = workflows
|
||||
.filter((item) => !item.config.global)
|
||||
.sort((a, b) => {
|
||||
const aIndex = triggerWorkflowsArray.indexOf(a.key);
|
||||
const bIndex = triggerWorkflowsArray.indexOf(b.key);
|
||||
if (aIndex === -1 && bIndex === -1) {
|
||||
return a.id - b.id;
|
||||
}
|
||||
if (aIndex === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (bIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
for (const workflow of localWorkflows.concat(globalWorkflows)) {
|
||||
if (!workflow.config.global && !triggerWorkflowsMap.has(workflow.key)) {
|
||||
continue;
|
||||
}
|
||||
const processor = await this.workflow.trigger(
|
||||
workflow,
|
||||
{
|
||||
user: context.state.currentUser,
|
||||
roleName: context.state.currentRole,
|
||||
params: {
|
||||
filterByTk,
|
||||
filter,
|
||||
values,
|
||||
},
|
||||
},
|
||||
{ httpContext: context },
|
||||
);
|
||||
if (!processor) {
|
||||
return context.throw(500);
|
||||
}
|
||||
const { lastSavedJob, nodesMap } = processor;
|
||||
const lastNode = nodesMap.get(lastSavedJob?.nodeId);
|
||||
if (processor.execution.status === EXECUTION_STATUS.RESOLVED) {
|
||||
if (lastNode?.type === 'end') {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (processor.execution.status < EXECUTION_STATUS.STARTED) {
|
||||
if (lastNode?.type !== 'end') {
|
||||
return context.throw(500, 'Workflow on your action failed, please contact the administrator');
|
||||
}
|
||||
const err = new RequestInterceptionError('Request is intercepted by workflow');
|
||||
err.status = 400;
|
||||
err.messages = context.state.messages;
|
||||
return context.throw(err.status, err);
|
||||
}
|
||||
return context.throw(500, 'Workflow on your action hangs, please contact the administrator');
|
||||
}
|
||||
await next();
|
||||
};
|
||||
constructor(workflow: PluginWorkflow) {
|
||||
super(workflow);
|
||||
workflow.app.use(this.middleware, { after: 'dataSource' });
|
||||
workflow.app.pm.get(PluginErrorHandler).errorHandler.register(
|
||||
(err) => err instanceof RequestInterceptionError,
|
||||
async (err, ctx, next) => {
|
||||
ctx.body = {
|
||||
errors: err.messages,
|
||||
};
|
||||
ctx.status = err.status;
|
||||
},
|
||||
);
|
||||
}
|
||||
on() {}
|
||||
off() {}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import PluginWorkflow from '@tachybase/plugin-workflow';
|
||||
import { RequestInterceptionTrigger } from './RequestInterceptionTrigger';
|
||||
import { ApiTrigger } from './ApiTrigger';
|
||||
|
||||
export class PluginInterception extends Plugin {
|
||||
async load() {
|
||||
const workflowPlugin = this.app.pm.get(PluginWorkflow) as PluginWorkflow;
|
||||
workflowPlugin.registerTrigger('request-interception', RequestInterceptionTrigger);
|
||||
workflowPlugin.registerTrigger('api', ApiTrigger);
|
||||
}
|
||||
}
|
@ -10,12 +10,15 @@ import './actions';
|
||||
import { Container } from '@nocobase/utils';
|
||||
import { DepartmentsPlugin } from './features/departments';
|
||||
import TstzrangeField from './fields/tstzrange';
|
||||
import { PluginInterception } from './features/interception';
|
||||
|
||||
export class PluginCoreServer extends Plugin {
|
||||
pluginDepartments: DepartmentsPlugin;
|
||||
pluginInterception: PluginInterception;
|
||||
constructor(app: Application, options?: PluginOptions) {
|
||||
super(app, options);
|
||||
this.pluginDepartments = new DepartmentsPlugin(app, options);
|
||||
this.pluginInterception = new PluginInterception(app, options);
|
||||
}
|
||||
async afterAdd() {
|
||||
this.db.registerFieldTypes({
|
||||
@ -28,6 +31,7 @@ export class PluginCoreServer extends Plugin {
|
||||
}
|
||||
async load() {
|
||||
await this.pluginDepartments.load();
|
||||
await this.pluginInterception.load();
|
||||
Container.reset();
|
||||
try {
|
||||
Container.set({ id: 'db', value: this.db });
|
||||
@ -47,7 +51,7 @@ export class PluginCoreServer extends Plugin {
|
||||
}
|
||||
}
|
||||
async install(options?: InstallOptions) {
|
||||
this.pluginDepartments.install(options);
|
||||
await this.pluginDepartments.install(options);
|
||||
}
|
||||
async afterEnable() {}
|
||||
async afterDisable() {}
|
||||
|
@ -2,6 +2,7 @@ import actions, { Context, utils } from '@nocobase/actions';
|
||||
import { Op, Repository } from '@nocobase/database';
|
||||
|
||||
import Plugin from '../Plugin';
|
||||
import { WorkflowModel } from '../types';
|
||||
|
||||
export async function update(context: Context, next) {
|
||||
const repository = utils.getRepositoryFromParams(context) as Repository;
|
||||
@ -160,6 +161,14 @@ export async function sync(context: Context, next) {
|
||||
await next();
|
||||
}
|
||||
|
||||
export async function trigger(context: Context, next) {
|
||||
return next();
|
||||
export async function trigger(ctx: Context) {
|
||||
const plugin = ctx.app.getPlugin(Plugin) as Plugin;
|
||||
const workflow = (await ctx.db.getRepository('workflows').findById(ctx.action.params.filterByTk)) as WorkflowModel;
|
||||
plugin.trigger(
|
||||
workflow,
|
||||
{
|
||||
data: {},
|
||||
},
|
||||
{ httpContext: ctx },
|
||||
);
|
||||
}
|
||||
|
@ -1507,6 +1507,9 @@ importers:
|
||||
'@nocobase/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/utils
|
||||
'@tachybase/plugin-workflow':
|
||||
specifier: workspace:*
|
||||
version: link:../../@tachybase/plugin-workflow
|
||||
devDependencies:
|
||||
'@ant-design/cssinjs':
|
||||
specifier: ^1.11.1
|
||||
@ -1517,9 +1520,9 @@ importers:
|
||||
'@ant-design/pro-layout':
|
||||
specifier: ^7.16.11
|
||||
version: 7.17.16(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@emotion/css':
|
||||
specifier: ^11.11.2
|
||||
version: 11.11.2
|
||||
'@formily/antd-v5':
|
||||
specifier: 1.x
|
||||
version: 1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5)
|
||||
'@react-pdf/renderer':
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.8(react@18.2.0)
|
||||
@ -1529,15 +1532,15 @@ importers:
|
||||
'@tachybase/schema':
|
||||
specifier: workspace:*
|
||||
version: link:../../../core/schema
|
||||
'@types/lodash':
|
||||
specifier: ~4.17.0
|
||||
version: 4.17.0
|
||||
ahooks:
|
||||
specifier: ^3.7.2
|
||||
version: 3.7.8(react@18.2.0)
|
||||
antd:
|
||||
specifier: 5.16.1
|
||||
version: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
antd-style:
|
||||
specifier: 3.4.5
|
||||
version: 3.4.5(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames:
|
||||
specifier: ^2.3.1
|
||||
version: 2.5.1
|
||||
@ -7511,6 +7514,19 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
|
||||
/@dnd-kit/core@6.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.0(react@18.2.0)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/@dnd-kit/modifiers@6.0.1(@dnd-kit/core@5.0.3)(react@18.2.0):
|
||||
resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==}
|
||||
peerDependencies:
|
||||
@ -7535,6 +7551,18 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.0.7
|
||||
react: '>=16.8.0'
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@dnd-kit/utilities': 3.2.2(react@18.2.0)
|
||||
react: 18.2.0
|
||||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/@dnd-kit/utilities@3.2.2(react@18.2.0):
|
||||
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||
peerDependencies:
|
||||
@ -8754,6 +8782,40 @@ packages:
|
||||
resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
|
||||
dev: true
|
||||
|
||||
/@formily/antd-v5@1.2.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(antd@5.16.1)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5):
|
||||
resolution: {integrity: sha512-6K1fV7pwvy7j5KoO/5dEe5llosann7211I/bH/WJkLQtDSDK+XLFUQ4/lU7CdNpwHMrspzXS3lF82EM3hGyM8w==}
|
||||
engines: {npm: '>=3.0.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8.0 || >=17.0.0'
|
||||
'@types/react-dom': '>=16.8.0 || >=17.0.0'
|
||||
antd: ^5.13.0
|
||||
react: '>=16.8.0 || >=17.0.0'
|
||||
react-dom: '>=16.8.0'
|
||||
react-is: '>=16.8.0 || >=17.0.0'
|
||||
dependencies:
|
||||
'@ant-design/cssinjs': 1.20.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@ant-design/icons': 5.3.6(react-dom@18.2.0)(react@18.2.0)
|
||||
'@dnd-kit/core': 6.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.1.0)(react@18.2.0)
|
||||
'@formily/core': 2.2.27
|
||||
'@formily/grid': 2.2.27(typescript@4.9.5)
|
||||
'@formily/json-schema': 2.2.27(typescript@4.9.5)
|
||||
'@formily/react': 2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)(typescript@4.9.5)
|
||||
'@formily/reactive': 2.2.27
|
||||
'@formily/reactive-react': 2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0)
|
||||
'@formily/shared': 2.2.27
|
||||
'@types/react': 18.2.79
|
||||
'@types/react-dom': 18.2.25
|
||||
antd: 5.16.1(react-dom@18.2.0)(react@18.2.0)
|
||||
classnames: 2.5.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-is: 18.3.1
|
||||
react-sticky-box: 1.0.2(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@formily/core@2.2.27:
|
||||
resolution: {integrity: sha512-f8e4f/WlimOA7zN1ARmiRKMlIpsIH00XxT7NWAE7EV3DO2B6sq6gSq9GDc5JLwHqhiyJBAIPzUdfmIpRLwPxyQ==}
|
||||
engines: {npm: '>=3.0.0'}
|
||||
@ -8771,7 +8833,6 @@ packages:
|
||||
'@formily/reactive': 2.2.27
|
||||
'@juggle/resize-observer': 3.4.0
|
||||
typescript: 4.9.5
|
||||
dev: false
|
||||
|
||||
/@formily/json-schema@2.2.27(typescript@4.9.5):
|
||||
resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==}
|
||||
@ -8783,7 +8844,6 @@ packages:
|
||||
'@formily/reactive': 2.2.27
|
||||
'@formily/shared': 2.2.27
|
||||
typescript: 4.9.5
|
||||
dev: false
|
||||
|
||||
/@formily/json-schema@2.2.27(typescript@5.4.5):
|
||||
resolution: {integrity: sha512-YqtpPp9bmXO28iw10ZLT5KCAqipr2SwPrzfskUJ/Oa8O3xZi64+9TMVPylX73cBe49lVaOgFiQPNAljLudeveg==}
|
||||
@ -8830,7 +8890,6 @@ packages:
|
||||
react-is: 18.3.1
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
/@formily/reactive-react@2.2.27(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0)(react-is@18.3.1)(react@18.2.0):
|
||||
resolution: {integrity: sha512-LJefK/9g8MorhGexMiVF+FVWyMR/Vc4C63Djrj08XeXtTF0NUNUUKVBT1eZqmtdRHJSyDE7SSgUmpxcXP7QmQQ==}
|
||||
@ -8854,7 +8913,6 @@ packages:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-is: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@formily/reactive@2.2.27:
|
||||
resolution: {integrity: sha512-ksdA4uIw3WMo0FmHcGsyqZgPwhujVgtXqxiWPSV+Tc2eEoJrInZhr15Q0IeTQ6glMh8izNsb19YK+PphBHwXGg==}
|
||||
@ -9105,7 +9163,6 @@ packages:
|
||||
|
||||
/@juggle/resize-observer@3.4.0:
|
||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||
dev: false
|
||||
|
||||
/@koa/cors@3.4.3:
|
||||
resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==}
|
||||
@ -13083,7 +13140,7 @@ packages:
|
||||
'@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4)
|
||||
react-refresh: 0.14.0
|
||||
vite: 4.5.2(@types/node@20.12.2)(less@4.1.3)
|
||||
vite: 4.5.2(@types/node@20.12.7)(less@4.1.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -26145,7 +26202,6 @@ packages:
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
resize-observer-polyfill: 1.5.1
|
||||
dev: false
|
||||
|
||||
/react-to-print@2.14.15(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-SKnwOzU2cJ8eaAkoJO7+gNhvfEDmm+Y34IdcHsjtHioUevUPhprqbVtvNJlZ2JkGJ8ExK2QNWM9pXECTDR5D8w==}
|
||||
@ -29961,6 +30017,7 @@ packages:
|
||||
rollup: 3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: false
|
||||
|
||||
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
|
||||
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
|
||||
@ -29997,7 +30054,6 @@ packages:
|
||||
rollup: 3.29.4
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
|
||||
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
|
||||
|
Loading…
Reference in New Issue
Block a user