Feat(plugin workflow): add changed fields to model trigger config (#332)
* feat(plugin-workflow): add changed fields to model trigger config * fix(plugin-workflow): avoid system fields to input
This commit is contained in:
parent
27399f4e5e
commit
f5cc368cef
@ -395,12 +395,7 @@ export const CollectionFieldset = observer(({ value, onChange, useProps }: any)
|
|||||||
}
|
}
|
||||||
`}>
|
`}>
|
||||||
{fields.length
|
{fields.length
|
||||||
? fields
|
? fields.map(field => {
|
||||||
.filter(field => (
|
|
||||||
!field.hidden
|
|
||||||
&& (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true)
|
|
||||||
))
|
|
||||||
.map(field => {
|
|
||||||
const operand = typeof value[field.name] === 'string'
|
const operand = typeof value[field.name] === 'string'
|
||||||
? parseStringValue(value[field.name], VTypes)
|
? parseStringValue(value[field.name], VTypes)
|
||||||
: { type: 'constant', value: value[field.name] };
|
: { type: 'constant', value: value[field.name] };
|
||||||
|
@ -12,8 +12,8 @@ export default {
|
|||||||
group: 'model',
|
group: 'model',
|
||||||
fieldset: {
|
fieldset: {
|
||||||
'config.collection': {
|
'config.collection': {
|
||||||
name: 'config.collection',
|
|
||||||
...collection,
|
...collection,
|
||||||
|
name: 'config.collection'
|
||||||
},
|
},
|
||||||
// multiple: {
|
// multiple: {
|
||||||
// type: 'boolean',
|
// type: 'boolean',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { CloseOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { CloseOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { Field } from '@formily/core';
|
import { ISchema, useForm } from '@formily/react';
|
||||||
import { ISchema, RecursionField, useField, useForm } from '@formily/react';
|
|
||||||
import { Registry } from '@nocobase/utils';
|
import { Registry } from '@nocobase/utils';
|
||||||
import { Button, Modal, Tag } from 'antd';
|
import { Button, Modal, Tag } from 'antd';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { SchemaComponent, useActionContext, useAPIClient, useCollection, useRequest, useResourceActionContext } from '../..';
|
import { SchemaComponent, useActionContext, useAPIClient, useCollection, useRequest, useResourceActionContext } from '../..';
|
||||||
import { nodeBlockClass, nodeCardClass, nodeClass, nodeHeaderClass, nodeTitleClass } from '../style';
|
import { nodeBlockClass, nodeCardClass, nodeClass, nodeHeaderClass, nodeTitleClass } from '../style';
|
||||||
import { AddButton, useFlowContext } from '../WorkflowCanvas';
|
import { AddButton, useFlowContext } from '../WorkflowCanvas';
|
||||||
|
|
||||||
import calculation from './calculation';
|
import calculation from './calculation';
|
||||||
import condition from './condition';
|
import condition from './condition';
|
||||||
import create from './create';
|
import create from './create';
|
||||||
@ -18,10 +18,6 @@ import update from './update';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Instruction {
|
export interface Instruction {
|
||||||
title: string;
|
title: string;
|
||||||
type: string;
|
type: string;
|
||||||
@ -148,14 +144,6 @@ export function RemoveButton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Fieldset({ schema }) {
|
|
||||||
const field = useField<Field>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RecursionField basePath={field.address} schema={schema} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NodeDefaultView(props) {
|
export function NodeDefaultView(props) {
|
||||||
const { data, children } = props;
|
const { data, children } = props;
|
||||||
const instruction = instructions.get(data.type);
|
const instruction = instructions.get(data.type);
|
||||||
@ -174,10 +162,7 @@ export function NodeDefaultView(props) {
|
|||||||
</div>
|
</div>
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={instruction.scope}
|
scope={instruction.scope}
|
||||||
components={{
|
components={instruction.components}
|
||||||
...instruction.components,
|
|
||||||
Fieldset
|
|
||||||
}}
|
|
||||||
schema={{
|
schema={{
|
||||||
type: 'void',
|
type: 'void',
|
||||||
properties: {
|
properties: {
|
||||||
@ -197,9 +182,9 @@ export function NodeDefaultView(props) {
|
|||||||
'x-decorator': 'Form',
|
'x-decorator': 'Form',
|
||||||
'x-decorator-props': {
|
'x-decorator-props': {
|
||||||
useValues(options) {
|
useValues(options) {
|
||||||
const data = useNodeContext();
|
const d = useNodeContext();
|
||||||
return useRequest(() => {
|
return useRequest(() => {
|
||||||
return Promise.resolve({ data });
|
return Promise.resolve({ data: d });
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -29,7 +29,11 @@ export const values = {
|
|||||||
useProps() {
|
useProps() {
|
||||||
const { getCollectionFields } = useCollectionManager();
|
const { getCollectionFields } = useCollectionManager();
|
||||||
const { values: form } = useForm();
|
const { values: form } = useForm();
|
||||||
const fields = getCollectionFields(form?.config?.collection);
|
const fields = getCollectionFields(form?.config?.collection)
|
||||||
|
.filter(field => (
|
||||||
|
!field.hidden
|
||||||
|
&& (field.uiSchema ? !field.uiSchema['x-read-pretty'] : false)
|
||||||
|
));
|
||||||
return { fields };
|
return { fields };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +54,7 @@ export const filter = {
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
useProps() {
|
useProps() {
|
||||||
const { values } = useForm();
|
const { values } = useForm();
|
||||||
const options = useCollectionFilterOptions(values.config.collection);
|
const options = useCollectionFilterOptions(values.config?.collection);
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
className: css`
|
className: css`
|
||||||
|
@ -3,7 +3,7 @@ import { ISchema, useForm } from "@formily/react";
|
|||||||
import { cx } from "@emotion/css";
|
import { cx } from "@emotion/css";
|
||||||
import { Registry } from "@nocobase/utils";
|
import { Registry } from "@nocobase/utils";
|
||||||
|
|
||||||
import { SchemaComponent, useActionContext, useAPIClient, useRecord, useResourceActionContext } from '../../';
|
import { SchemaComponent, useActionContext, useAPIClient, useRecord, useRequest, useResourceActionContext } from '../../';
|
||||||
import model from './model';
|
import model from './model';
|
||||||
import { nodeCardClass } from "../style";
|
import { nodeCardClass } from "../style";
|
||||||
|
|
||||||
@ -19,12 +19,7 @@ function useUpdateConfigAction() {
|
|||||||
await form.submit();
|
await form.submit();
|
||||||
await api.resource('workflows', record.id).update({
|
await api.resource('workflows', record.id).update({
|
||||||
filterByTk: record.id,
|
filterByTk: record.id,
|
||||||
values: {
|
values: form.values
|
||||||
config: {
|
|
||||||
...record.config,
|
|
||||||
...form.values
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
ctx.setVisible(false);
|
ctx.setVisible(false);
|
||||||
refresh();
|
refresh();
|
||||||
@ -72,10 +67,16 @@ export const TriggerConfig = () => {
|
|||||||
'x-component': 'Action.Drawer',
|
'x-component': 'Action.Drawer',
|
||||||
'x-decorator': 'Form',
|
'x-decorator': 'Form',
|
||||||
'x-decorator-props': {
|
'x-decorator-props': {
|
||||||
initialValue: config
|
initialValue: { config }
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
...fieldset,
|
config: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'config',
|
||||||
|
'x-component': 'fieldset',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: fieldset
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
|
import { action } from '@formily/reactive';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
|
||||||
import { useCollectionDataSource, useCollectionManager } from '../../collection-manager';
|
import { useCollectionDataSource, useCollectionManager } from '../../collection-manager';
|
||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
@ -8,15 +10,36 @@ import { useFlowContext } from '../WorkflowCanvas';
|
|||||||
import { BaseTypeSet } from '../calculators';
|
import { BaseTypeSet } from '../calculators';
|
||||||
import { collection, filter } from '../schemas/collection';
|
import { collection, filter } from '../schemas/collection';
|
||||||
|
|
||||||
|
function useCollectionFieldsDataSource() {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
const { values } = useForm();
|
||||||
|
const fields = getCollectionFields(values?.config?.collection);
|
||||||
|
|
||||||
|
return (field: any) => {
|
||||||
|
action.bound((data: any) => {
|
||||||
|
field.dataSource = data
|
||||||
|
.filter(field => (
|
||||||
|
!field.hidden
|
||||||
|
&& (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true)
|
||||||
|
))
|
||||||
|
.map(field => ({
|
||||||
|
label: compile(field.uiSchema?.title),
|
||||||
|
value: field.name
|
||||||
|
}));
|
||||||
|
})(fields);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: '数据表事件',
|
title: '数据表事件',
|
||||||
type: 'model',
|
type: 'model',
|
||||||
fieldset: {
|
fieldset: {
|
||||||
collection,
|
'config.collection': collection,
|
||||||
mode: {
|
'config.mode': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
title: '触发时机',
|
title: '触发时机',
|
||||||
name: 'mode',
|
name: 'config.mode',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Select',
|
'x-component': 'Select',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
@ -28,13 +51,29 @@ export default {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filter: {
|
'config.changed': {
|
||||||
|
type: 'array',
|
||||||
|
name: 'changed',
|
||||||
|
title: '发生变动的字段',
|
||||||
|
description: '只有被选中的某个字段发生变动时才会触发。如果不选择,则表示任何字段变动时都会触发。新增或删除数据时,任意字段都被认为发生变动。',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'multiple',
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
'{{useCollectionFieldsDataSource()}}'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'config.condition': {
|
||||||
...filter,
|
...filter,
|
||||||
|
name: 'config.condition',
|
||||||
title: '满足条件'
|
title: '满足条件'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scope: {
|
scope: {
|
||||||
useCollectionDataSource
|
useCollectionDataSource,
|
||||||
|
useCollectionFieldsDataSource
|
||||||
},
|
},
|
||||||
getter({ type, options, onChange }) {
|
getter({ type, options, onChange }) {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
@ -4,7 +4,7 @@ export interface ModelChangeTriggerConfig {
|
|||||||
collection: string;
|
collection: string;
|
||||||
mode: number;
|
mode: number;
|
||||||
// TODO: ICondition
|
// TODO: ICondition
|
||||||
filter: any;
|
condition: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODE_BITMAP = {
|
const MODE_BITMAP = {
|
||||||
@ -18,26 +18,37 @@ MODE_BITMAP_EVENTS.set(MODE_BITMAP.CREATE, 'afterCreate');
|
|||||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.UPDATE, 'afterUpdate');
|
MODE_BITMAP_EVENTS.set(MODE_BITMAP.UPDATE, 'afterUpdate');
|
||||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.DESTROY, 'afterDestroy');
|
MODE_BITMAP_EVENTS.set(MODE_BITMAP.DESTROY, 'afterDestroy');
|
||||||
|
|
||||||
|
// async function, should return promise
|
||||||
|
function bindHandler(this: WorkflowModel, callback: Function) {
|
||||||
|
const { condition, changed } = this.config;
|
||||||
|
return (data: any, options) => {
|
||||||
|
// NOTE: if no configured fields changed, do not trigger
|
||||||
|
if (changed && changed.length && changed.every(name => !data.changed(name))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// NOTE: if no configured condition match, do not trigger
|
||||||
|
if (condition && condition.$and.length) {
|
||||||
|
// TODO: check all conditions in condition against data
|
||||||
|
// const calculation = toCalculation(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback({ data: data.get() }, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'model',
|
name: 'model',
|
||||||
on(this: WorkflowModel, callback: Function) {
|
on(this: WorkflowModel, callback: Function) {
|
||||||
const { database } = <typeof WorkflowModel>this.constructor;
|
const { database } = <typeof WorkflowModel>this.constructor;
|
||||||
const { collection, mode, filter } = this.config;
|
const { collection, mode } = this.config;
|
||||||
const Collection = database.getCollection(collection);
|
const Collection = database.getCollection(collection);
|
||||||
if (!Collection) {
|
if (!Collection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// async function, should return promise
|
|
||||||
const handler = (data: any, options) => {
|
|
||||||
if (filter) {
|
|
||||||
// TODO: check all conditions in filter against data
|
|
||||||
}
|
|
||||||
return callback({ data: data.get() }, options);
|
|
||||||
};
|
|
||||||
// TODO: duplication when mode change should be considered
|
// TODO: duplication when mode change should be considered
|
||||||
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
||||||
if (mode & key) {
|
if (mode & key) {
|
||||||
Collection.model.addHook(event, this.getHookId(), handler);
|
Collection.model.addHook(event, this.getHookId(), bindHandler.call(this, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user