refactor(plugin-workflow): change variable getter from collection fields (#4567)

* refactor(plugin-workflow): change variable getter from collection fields

* fix(plugin-workflow): fix import

* chore(plugin-workflow-action-trigger): remove unused import

* fix(plugin-workflow): fix collection field in workflow variable

* refactor(plugin-workflow-manual): avoid tslint error
This commit is contained in:
Junyi 2024-06-10 16:30:43 +08:00 committed by GitHub
parent 1d2ac6b85e
commit 792200278e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 274 additions and 267 deletions

View File

@ -9,12 +9,7 @@
import { useForm } from '@formily/react';
import {
SchemaInitializerItemType,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@nocobase/client';
import { SchemaInitializerItemType, parseCollectionName, useCollectionDataSource, useCompile } from '@nocobase/client';
import {
Trigger,
CollectionBlockInitializer,
@ -22,6 +17,7 @@ import {
useWorkflowAnyExecuted,
CheckboxGroupWithTooltip,
RadioWithTooltip,
useGetCollectionFields,
} from '@nocobase/plugin-workflow/client';
import { NAMESPACE, useLang } from '../locale';
@ -32,6 +28,62 @@ const COLLECTION_TRIGGER_ACTION = {
DESTROY: 'destroy',
};
function useVariables(config, options) {
const [dataSourceName, collection] = parseCollectionName(config.collection);
const compile = useCompile();
const getCollectionFields = useGetCollectionFields(dataSourceName);
const getMainCollectionFields = useGetCollectionFields();
const langTriggerData = useLang('Trigger data');
const langUserSubmittedForm = useLang('User submitted action');
const langRoleSubmittedForm = useLang('Role of user submitted action');
const result = [
...getCollectionFieldOptions({
// depth,
appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])],
...options,
fields: [
{
collectionName: collection,
name: 'data',
type: 'hasOne',
target: collection,
uiSchema: {
title: langTriggerData,
},
},
],
compile,
getCollectionFields,
}),
...getCollectionFieldOptions({
// depth,
appends: ['user'],
...options,
fields: [
{
collectionName: 'users',
name: 'user',
type: 'hasOne',
target: 'users',
uiSchema: {
title: langUserSubmittedForm,
},
},
{
name: 'roleName',
uiSchema: {
title: langRoleSubmittedForm,
},
},
],
compile,
getCollectionFields: getMainCollectionFields,
}),
];
return result;
}
export default class extends Trigger {
title = `{{t("Post-action event", { ns: "${NAMESPACE}" })}}`;
description = `{{t('Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, or deleting data. Suitable for data processing, sending notifications, etc., after actions are completed.', { ns: "${NAMESPACE}" })}}`;
@ -153,53 +205,7 @@ export default class extends Trigger {
isActionTriggerable = (config, context) => {
return !config.global && ['submit', 'customize:save', 'customize:update'].includes(context.buttonAction);
};
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 = useLang('Trigger data');
// eslint-disable-next-line react-hooks/rules-of-hooks
const langUserSubmittedForm = useLang('User submitted action');
// eslint-disable-next-line react-hooks/rules-of-hooks
const langRoleSubmittedForm = useLang('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;
}
useVariables = useVariables;
useInitializers(config): SchemaInitializerItemType | null {
if (!config.collection) {
return null;

View File

@ -9,7 +9,7 @@
import { SchemaInitializerItemType, useCollectionManager_deprecated, useCompile, usePlugin } from '@nocobase/client';
import WorkflowPlugin, {
import {
defaultFieldNames,
getCollectionFieldOptions,
CollectionBlockInitializer,
@ -29,6 +29,47 @@ const MULTIPLE_ASSIGNED_MODE = {
ANY_PERCENTAGE: Symbol('any percentage'),
};
function useVariables({ key, title, config }, { types, fieldNames = defaultFieldNames }) {
const compile = useCompile();
const { getCollectionFields } = useCollectionManager_deprecated();
const formKeys = Object.keys(config.forms ?? {});
if (!formKeys.length) {
return null;
}
const options = formKeys
.map((formKey) => {
const form = config.forms[formKey];
const fieldsOptions = getCollectionFieldOptions({
fields: form.collection?.fields,
collection: form.collection,
types,
compile,
getCollectionFields,
});
const label = compile(form.title) || formKey;
return fieldsOptions.length
? {
key: formKey,
value: formKey,
label,
title: label,
children: fieldsOptions,
}
: null;
})
.filter(Boolean);
return options.length
? {
[fieldNames.value]: key,
[fieldNames.label]: title,
[fieldNames.children]: options,
}
: null;
}
export default class extends Instruction {
title = `{{t("Manual", { ns: "${NAMESPACE}" })}}`;
type = 'manual';
@ -85,48 +126,7 @@ export default class extends Instruction {
ModeConfig,
AssigneesSelect,
};
useVariables({ key, title, config }, { types, fieldNames = defaultFieldNames }) {
// 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 formKeys = Object.keys(config.forms ?? {});
if (!formKeys.length) {
return null;
}
const options = formKeys
.map((formKey) => {
const form = config.forms[formKey];
const fieldsOptions = getCollectionFieldOptions({
fields: form.collection?.fields,
collection: form.collection,
types,
compile,
getCollectionFields,
});
const label = compile(form.title) || formKey;
return fieldsOptions.length
? {
key: formKey,
value: formKey,
label,
title: label,
children: fieldsOptions,
}
: null;
})
.filter(Boolean);
return options.length
? {
[fieldNames.value]: key,
[fieldNames.label]: title,
[fieldNames.children]: options,
}
: null;
}
useVariables = useVariables;
useInitializers(node): SchemaInitializerItemType | null {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { getCollection } = useCollectionManager_deprecated();

View File

@ -7,20 +7,45 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import {
SchemaInitializerItemType,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@nocobase/client';
import { SchemaInitializerItemType, parseCollectionName, useCollectionDataSource, useCompile } from '@nocobase/client';
import { CollectionBlockInitializer } from '../components/CollectionBlockInitializer';
import CollectionFieldset from '../components/CollectionFieldset';
import { NAMESPACE } from '../locale';
import { appends, collection, values } from '../schemas/collection';
import { getCollectionFieldOptions } from '../variable';
import { getCollectionFieldOptions, useGetCollectionFields } from '../variable';
import { Instruction } from '.';
function useVariables({ key: name, title, config }, options) {
const [dataSourceName, collection] = parseCollectionName(config.collection);
const compile = useCompile();
const getCollectionFields = useGetCollectionFields(dataSourceName);
// const depth = config?.params?.appends?.length
// ? config?.params?.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1)
// : 0;
const [result] = getCollectionFieldOptions({
// collection: config.collection,
// depth: options?.depth ?? depth,
appends: [name, ...(config.params?.appends?.map((item) => `${name}.${item}`) || [])],
...options,
fields: [
{
collectionName: collection,
name,
type: 'hasOne',
target: collection,
uiSchema: {
title,
},
},
],
compile,
getCollectionFields,
});
return result;
}
export default class extends Instruction {
title = `{{t("Create record", { ns: "${NAMESPACE}" })}}`;
type = 'create';
@ -67,36 +92,7 @@ export default class extends Instruction {
components = {
CollectionFieldset,
};
useVariables({ key: name, title, 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 depth = config?.params?.appends?.length
// ? config?.params?.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1)
// : 0;
const [result] = getCollectionFieldOptions({
// collection: config.collection,
// depth: options?.depth ?? depth,
appends: [name, ...(config.params?.appends?.map((item) => `${name}.${item}`) || [])],
...options,
fields: [
{
collectionName: config.collection,
name,
type: 'hasOne',
target: config.collection,
uiSchema: {
title,
},
},
],
compile,
getCollectionFields,
});
return result;
}
useVariables = useVariables;
useInitializers(node): SchemaInitializerItemType | null {
if (!node.config.collection) {
return null;

View File

@ -12,8 +12,9 @@ import { ArrayItems } from '@formily/antd-v5';
import {
SchemaComponentContext,
SchemaInitializerItemType,
parseCollectionName,
useApp,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@nocobase/client';
@ -22,10 +23,40 @@ import { CollectionBlockInitializer } from '../components/CollectionBlockInitial
import { FilterDynamicComponent } from '../components/FilterDynamicComponent';
import { NAMESPACE } from '../locale';
import { appends, collection, filter, pagination, sort } from '../schemas/collection';
import { WorkflowVariableInput, getCollectionFieldOptions } from '../variable';
import { WorkflowVariableInput, getCollectionFieldOptions, useGetCollectionFields } from '../variable';
import { Instruction } from '.';
import { RadioWithTooltip } from '../components';
function useVariables({ key: name, title, config }, options) {
const [dataSourceName, collection] = parseCollectionName(config.collection);
const compile = useCompile();
const getCollectionFields = useGetCollectionFields(dataSourceName);
// const depth = config?.params?.appends?.length
// ? config?.params?.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1)
// : 0;
const [result] = getCollectionFieldOptions({
// collection: config.collection,
// depth: options?.depth ?? depth,
appends: [name, ...(config.params?.appends?.map((item) => `${name}.${item}`) || [])],
...options,
fields: [
{
collectionName: collection,
name,
type: 'hasOne',
target: collection,
uiSchema: {
title,
},
},
],
compile,
getCollectionFields,
});
return result;
}
export default class extends Instruction {
title = `{{t("Query record", { ns: "${NAMESPACE}" })}}`;
type = 'query';
@ -101,15 +132,19 @@ export default class extends Instruction {
useCollectionDataSource,
useSortableFields() {
const compile = useCompile();
const { getCollectionFields, getInterface } = useCollectionManager_deprecated();
const { values } = useForm();
const fields = getCollectionFields(values.collection);
const [dataSourceName, collection] = parseCollectionName(values.collection);
const app = useApp();
const { collectionManager } = app.dataSourceManager.getDataSource(dataSourceName);
const fields = collectionManager.getCollectionFields(collection);
return fields
.filter((field: any) => {
if (!field.interface) {
return false;
}
const fieldInterface = getInterface(field.interface);
const fieldInterface = app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface(
field.interface,
);
if (fieldInterface?.sortable) {
return true;
}
@ -130,36 +165,7 @@ export default class extends Instruction {
WorkflowVariableInput,
RadioWithTooltip,
};
useVariables({ key: name, title, 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 depth = config?.params?.appends?.length
// ? config?.params?.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1)
// : 0;
const [result] = getCollectionFieldOptions({
// collection: config.collection,
// depth: options?.depth ?? depth,
appends: [name, ...(config.params?.appends?.map((item) => `${name}.${item}`) || [])],
...options,
fields: [
{
collectionName: config.collection,
name,
type: 'hasOne',
target: config.collection,
uiSchema: {
title,
},
},
],
compile,
getCollectionFields,
});
return result;
}
useVariables = useVariables;
useInitializers(node): SchemaInitializerItemType | null {
if (!node.config.collection || node.config.multiple) {
return null;

View File

@ -7,17 +7,12 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import {
SchemaInitializerItemType,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@nocobase/client';
import { SchemaInitializerItemType, parseCollectionName, useCollectionDataSource, useCompile } from '@nocobase/client';
import { CollectionBlockInitializer } from '../components/CollectionBlockInitializer';
import { FieldsSelect } from '../components/FieldsSelect';
import { NAMESPACE, lang } from '../locale';
import { appends, collection, filter } from '../schemas/collection';
import { getCollectionFieldOptions } from '../variable';
import { getCollectionFieldOptions, useGetCollectionFields } from '../variable';
import { useWorkflowAnyExecuted } from '../hooks';
import { Trigger } from '.';
@ -35,6 +30,36 @@ const collectionModeOptions = [
{ label: `{{t("After record deleted", { ns: "${NAMESPACE}" })}}`, value: COLLECTION_TRIGGER_MODE.DELETED },
];
function useVariables(config, options) {
const [dataSourceName, collection] = parseCollectionName(config.collection);
const compile = useCompile();
const getCollectionFields = useGetCollectionFields(dataSourceName);
const rootFields = [
{
collectionName: collection,
name: 'data',
type: 'hasOne',
target: collection,
uiSchema: {
title: lang('Trigger data'),
},
},
];
// const depth = config.appends?.length
// ? config.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1) + 1
// : 1;
const result = getCollectionFieldOptions({
// depth,
appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])],
...options,
fields: rootFields,
compile,
getCollectionFields,
});
return result;
}
export default class extends Trigger {
title = `{{t("Collection event", { ns: "${NAMESPACE}" })}}`;
description = `{{t('Triggered when data changes in the collection, such as after adding, updating, or deleting a record. Unlike "Post-action event", Collection event listens for data changes rather than HTTP requests. Unless you understand the exact meaning, it is recommended to use "Post-action event".', { ns: "${NAMESPACE}" })}}`;
@ -166,35 +191,7 @@ export default class extends Trigger {
components = {
FieldsSelect,
};
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 rootFields = [
{
collectionName: config.collection,
name: 'data',
type: 'hasOne',
target: config.collection,
uiSchema: {
title: lang('Trigger data'),
},
},
];
// const depth = config.appends?.length
// ? config.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1) + 1
// : 1;
const result = getCollectionFieldOptions({
// depth,
appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])],
...options,
fields: rootFields,
compile,
getCollectionFields,
});
return result;
}
useVariables = useVariables;
useInitializers(config): SchemaInitializerItemType | null {
if (!config.collection) {
return null;

View File

@ -7,20 +7,54 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import {
SchemaInitializerItemType,
useCollectionDataSource,
useCollectionManager_deprecated,
useCompile,
} from '@nocobase/client';
import { SchemaInitializerItemType, parseCollectionName, useCollectionDataSource, useCompile } from '@nocobase/client';
import { CollectionBlockInitializer } from '../../components/CollectionBlockInitializer';
import { NAMESPACE, lang } from '../../locale';
import { getCollectionFieldOptions } from '../../variable';
import { getCollectionFieldOptions, useGetCollectionFields } from '../../variable';
import { Trigger } from '..';
import { ScheduleConfig } from './ScheduleConfig';
import { SCHEDULE_MODE } from './constants';
function useVariables(config, opts) {
const [dataSourceName, collection] = parseCollectionName(config.collection);
const compile = useCompile();
const getCollectionFields = useGetCollectionFields(dataSourceName);
const options: any[] = [];
if (!opts?.types || opts.types.includes('date')) {
options.push({ key: 'date', value: 'date', label: lang('Trigger time') });
}
// const depth = config.appends?.length
// ? config.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1) + 1
// : 1;
if (config.mode === SCHEDULE_MODE.DATE_FIELD) {
const [fieldOption] = getCollectionFieldOptions({
// depth,
appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])],
...opts,
fields: [
{
collectionName: config.collection,
name: 'data',
type: 'hasOne',
target: config.collection,
uiSchema: {
title: lang('Trigger data'),
},
},
],
compile,
getCollectionFields,
});
if (fieldOption) {
options.push(fieldOption);
}
}
return options;
}
export default class extends Trigger {
sync = false;
title = `{{t("Schedule event", { ns: "${NAMESPACE}" })}}`;
@ -38,45 +72,7 @@ export default class extends Trigger {
components = {
ScheduleConfig,
};
useVariables(config, opts) {
// 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 options: any[] = [];
if (!opts?.types || opts.types.includes('date')) {
options.push({ key: 'date', value: 'date', label: lang('Trigger time') });
}
// const depth = config.appends?.length
// ? config.appends.reduce((max, item) => Math.max(max, item.split('.').length), 1) + 1
// : 1;
if (config.mode === SCHEDULE_MODE.DATE_FIELD) {
const [fieldOption] = getCollectionFieldOptions({
// depth,
appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])],
...opts,
fields: [
{
collectionName: config.collection,
name: 'data',
type: 'hasOne',
target: config.collection,
uiSchema: {
title: lang('Trigger data'),
},
},
],
compile,
getCollectionFields,
});
if (fieldOption) {
options.push(fieldOption);
}
}
return options;
}
useVariables = useVariables;
useInitializers(config): SchemaInitializerItemType | null {
if (!config.collection) {
return null;

View File

@ -7,9 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React from 'react';
import React, { useCallback } from 'react';
import { uniqBy } from 'lodash';
import { Variable, parseCollectionName, useCompile, usePlugin } from '@nocobase/client';
import { Variable, parseCollectionName, useApp, useCompile, usePlugin } from '@nocobase/client';
import { useFlowContext } from './FlowContext';
import { NAMESPACE, lang } from './locale';
@ -259,17 +260,25 @@ export function useWorkflowVariableOptions(options: UseVariableOptions = {}) {
}
function getNormalizedFields(collectionName, { compile, getCollectionFields }) {
// NOTE: for compatibility with legacy version
const [dataSourceName, collection] = parseCollectionName(collectionName);
// NOTE: `dataSourceName` will be ignored in new version
const fields = getCollectionFields(collection, dataSourceName);
const foreignKeyFields: any[] = [];
console.log('--------------', fields);
const fkFields: any[] = [];
const result: any[] = [];
fields.forEach((field) => {
if (field.isForeignKey) {
foreignKeyFields.push(field);
fkFields.push(field);
} else {
const fkField = fields.find((f) => f.name === field.foreignKey);
if (fkField) {
fkFields.push(fkField);
}
result.push(field);
}
});
const foreignKeyFields = uniqBy(fkFields, 'name');
for (let i = result.length - 1; i >= 0; i--) {
const field = result[i];
if (field.type === 'belongsTo') {
@ -280,7 +289,7 @@ function getNormalizedFields(collectionName, { compile, getCollectionFields }) {
...foreignKeyField,
uiSchema: {
...field.uiSchema,
title: field.uiSchema?.title ? `${compile(field.uiSchema?.title)} ID` : foreignKeyField.name,
title: foreignKeyField.uiSchema?.title ? compile(foreignKeyField.uiSchema?.title) : foreignKeyField.name,
},
});
} else {
@ -297,21 +306,11 @@ function getNormalizedFields(collectionName, { compile, getCollectionFields }) {
});
}
} else if (field.type === 'context' && field.collectionName === 'users') {
const belongsToField =
result.find((f) => f.type === 'belongsTo' && f.target === 'users' && f.foreignKey === field.name) ?? {};
result.splice(i, 0, {
...field,
type: field.dataType,
interface: belongsToField.interface,
uiSchema: {
...belongsToField.uiSchema,
title: belongsToField.uiSchema?.title ? `${compile(belongsToField.uiSchema?.title)} ID` : field.name,
},
});
result.splice(i, 1);
}
}
return result.filter((field) => field.interface && !field.hidden);
return uniqBy(result, 'name').filter((field) => field.interface && !field.hidden);
}
function loadChildren(option) {
@ -381,6 +380,13 @@ export function getCollectionFieldOptions(options): VariableOption[] {
return result;
}
export function useGetCollectionFields(dataSourceName?) {
const app = useApp();
const { collectionManager } = app.dataSourceManager.getDataSource(dataSourceName);
return useCallback((collectionName) => collectionManager.getCollectionFields(collectionName), [collectionManager]);
}
export function WorkflowVariableInput({ variableOptions, ...props }): JSX.Element {
const scope = useWorkflowVariableOptions(variableOptions);
return <Variable.Input scope={scope} {...props} />;