mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 15:35:28 +00:00
test: add CollectionManager tests
This commit is contained in:
commit
9ab6785f69
2
.github/workflows/nocobase-test-e2e.yml
vendored
2
.github/workflows/nocobase-test-e2e.yml
vendored
@ -74,4 +74,4 @@ jobs:
|
||||
DB_USER: nocobase
|
||||
DB_PASSWORD: password
|
||||
DB_DATABASE: nocobase
|
||||
timeout-minutes: 80
|
||||
timeout-minutes: 100
|
||||
|
@ -15,7 +15,6 @@ import { useFilterBlock } from '../../filter-provider/FilterProvider';
|
||||
import { transformToFilter } from '../../filter-provider/utils';
|
||||
import { useRecord } from '../../record-provider';
|
||||
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
|
||||
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
|
||||
import { useCurrentUserContext } from '../../user';
|
||||
import { useLocalVariables, useVariables } from '../../variables';
|
||||
import { isVariable } from '../../variables/utils/isVariable';
|
||||
@ -680,94 +679,6 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useCustomizeBulkEditActionProps = () => {
|
||||
const form = useForm();
|
||||
const { t } = useTranslation();
|
||||
const { field, resource, __parent } = useBlockRequestContext();
|
||||
const expressionScope = useContext(SchemaExpressionScopeContext);
|
||||
const actionContext = useActionContext();
|
||||
const navigate = useNavigate();
|
||||
const compile = useCompile();
|
||||
const actionField = useField();
|
||||
const tableBlockContext = useTableBlockContext();
|
||||
const { modal } = App.useApp();
|
||||
|
||||
const { rowKey } = tableBlockContext;
|
||||
const selectedRecordKeys =
|
||||
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
||||
const { setVisible, fieldSchema: actionSchema } = actionContext;
|
||||
return {
|
||||
async onClick() {
|
||||
const { onSuccess, skipValidator, updateMode } = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { filter } = __parent.service.params?.[0] ?? {};
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
const values = cloneDeep(form.values);
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
for (const key in values) {
|
||||
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
||||
const value = values[key];
|
||||
if (BulkEditFormItemValueType.Clear in value) {
|
||||
values[key] = null;
|
||||
} else if (BulkEditFormItemValueType.ChangedTo in value) {
|
||||
values[key] = value[BulkEditFormItemValueType.ChangedTo];
|
||||
} else if (BulkEditFormItemValueType.RemainsTheSame in value) {
|
||||
delete values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
const updateData: { filter?: any; values: any; forceUpdate: boolean } = {
|
||||
values,
|
||||
filter,
|
||||
forceUpdate: false,
|
||||
};
|
||||
if (updateMode === 'selected') {
|
||||
if (!selectedRecordKeys?.length) {
|
||||
message.error(t('Please select the records to be updated'));
|
||||
return;
|
||||
}
|
||||
updateData.filter = { $and: [{ [rowKey || 'id']: { $in: selectedRecordKeys } }] };
|
||||
}
|
||||
if (!updateData.filter) {
|
||||
updateData.forceUpdate = true;
|
||||
}
|
||||
await resource.update(updateData);
|
||||
actionField.data.loading = false;
|
||||
if (!(resource instanceof TableFieldResource)) {
|
||||
__parent?.__parent?.service?.refresh?.();
|
||||
}
|
||||
__parent?.service?.refresh?.();
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
navigate(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
} finally {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useCustomizeRequestActionProps = () => {
|
||||
const apiClient = useAPIClient();
|
||||
const navigate = useNavigate();
|
||||
|
@ -30,6 +30,8 @@ const subMenuDesignerCss = css`
|
||||
padding: 0 34px 0 24px;
|
||||
width: calc(100% + 58px);
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
> .general-schema-designer {
|
||||
display: block;
|
||||
|
@ -282,119 +282,3 @@ export const updateFormActionInitializers = new SchemaInitializer({
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const bulkEditFormActionInitializers = new SchemaInitializer({
|
||||
name: 'BulkEditFormActionInitializers',
|
||||
title: '{{t("Configure actions")}}',
|
||||
icon: 'SettingOutlined',
|
||||
items: [
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Enable actions")}}',
|
||||
name: 'enableActions',
|
||||
children: [
|
||||
{
|
||||
name: 'submit',
|
||||
title: '{{t("Submit")}}',
|
||||
Component: 'BulkEditSubmitActionInitializer',
|
||||
schema: {
|
||||
'x-action-settings': {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'subMenu',
|
||||
title: '{{t("Customize")}}',
|
||||
name: 'customize',
|
||||
children: [
|
||||
{
|
||||
name: 'popup',
|
||||
title: '{{t("Popup")}}',
|
||||
Component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'saveRecord',
|
||||
title: '{{t("Save record")}}',
|
||||
Component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save") }}',
|
||||
'x-component': 'Action',
|
||||
'x-action': 'customize:save',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-designer-props': {
|
||||
modalTip:
|
||||
'{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}',
|
||||
},
|
||||
'x-action-settings': {
|
||||
assignedValues: {},
|
||||
skipValidator: false,
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Submitted successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useUpdateActionProps }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -1,3 +1,2 @@
|
||||
export * from './assigned-field';
|
||||
export * from './BulkEditField';
|
||||
export * from './CreateRecordAction';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Plugin } from '../application/Plugin';
|
||||
import {
|
||||
blockInitializers,
|
||||
bulkEditFormActionInitializers,
|
||||
createFormActionInitializers,
|
||||
createFormBlockInitializers,
|
||||
cusomeizeCreateFormBlockInitializers,
|
||||
@ -73,7 +72,6 @@ export class SchemaInitializerPlugin extends Plugin {
|
||||
this.app.schemaInitializerManager.add(filterFormActionInitializers);
|
||||
this.app.schemaInitializerManager.add(createFormActionInitializers);
|
||||
this.app.schemaInitializerManager.add(updateFormActionInitializers);
|
||||
this.app.schemaInitializerManager.add(bulkEditFormActionInitializers);
|
||||
this.app.schemaInitializerManager.add(filterFormItemInitializers);
|
||||
this.app.schemaInitializerManager.add(gridCardActionInitializers);
|
||||
this.app.schemaInitializerManager.add(gridCardItemActionInitializers);
|
||||
|
@ -16,7 +16,7 @@ export const FilterFormBlockInitializer = ({ filterMenuItemChildren, isItem }) =
|
||||
const s = createFilterFormBlockSchema({
|
||||
template: templateSchema,
|
||||
dataSource: item.dataSource,
|
||||
collection: item.collectionName,
|
||||
collection: item.name || item.collectionName,
|
||||
});
|
||||
if (item.template && item.mode === 'reference') {
|
||||
s['x-template-key'] = item.template.key;
|
||||
|
@ -6,7 +6,6 @@ export * from '../../schema-component/antd/association-filter/AssociationFilterD
|
||||
export * from './ActionInitializer';
|
||||
export * from './BlockInitializer';
|
||||
export * from './BulkDestroyActionInitializer';
|
||||
export * from './BulkEditSubmitActionInitializer';
|
||||
export * from './CollectionFieldInitializer';
|
||||
export * from './CreateActionInitializer';
|
||||
export * from './CreateChildInitializer';
|
||||
|
@ -0,0 +1,116 @@
|
||||
import { SchemaInitializer } from '@nocobase/client';
|
||||
export const BulkEditFormActionInitializers = new SchemaInitializer({
|
||||
name: 'BulkEditFormActionInitializers',
|
||||
title: '{{t("Configure actions")}}',
|
||||
icon: 'SettingOutlined',
|
||||
items: [
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Enable actions")}}',
|
||||
name: 'enableActions',
|
||||
children: [
|
||||
{
|
||||
name: 'submit',
|
||||
title: '{{t("Submit")}}',
|
||||
Component: 'BulkEditSubmitActionInitializer',
|
||||
schema: {
|
||||
'x-action-settings': {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'subMenu',
|
||||
title: '{{t("Customize")}}',
|
||||
name: 'customize',
|
||||
children: [
|
||||
{
|
||||
name: 'popup',
|
||||
title: '{{t("Popup")}}',
|
||||
Component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-action': 'customize:popup',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: '{{ t("Popup") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'RecordBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'saveRecord',
|
||||
title: '{{t("Save record")}}',
|
||||
Component: 'CustomizeActionInitializer',
|
||||
schema: {
|
||||
title: '{{ t("Save") }}',
|
||||
'x-component': 'Action',
|
||||
'x-action': 'customize:save',
|
||||
'x-designer': 'Action.Designer',
|
||||
'x-designer-props': {
|
||||
modalTip:
|
||||
'{{ t("When the button is clicked, the following fields will be assigned and saved together with the fields in the form. If there are overlapping fields, the value here will overwrite the value in the form.") }}',
|
||||
},
|
||||
'x-action-settings': {
|
||||
assignedValues: {},
|
||||
skipValidator: false,
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Submitted successfully")}}',
|
||||
},
|
||||
},
|
||||
'x-component-props': {
|
||||
useProps: '{{ useUpdateActionProps }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customRequest',
|
||||
title: '{{t("Custom request")}}',
|
||||
Component: 'CustomRequestInitializer',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
@ -2,14 +2,19 @@ import { SchemaComponentOptions } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { CustomizeBulkEditActionInitializer } from './CustomizeBulkEditActionInitializer';
|
||||
import { CreateFormBulkEditBlockInitializer } from './CreateFormBulkEditBlockInitializer';
|
||||
|
||||
import { BulkEditSubmitActionInitializer } from './BulkEditSubmitActionInitializer';
|
||||
import { useCustomizeBulkEditActionProps } from './utils';
|
||||
import { BulkEditField } from './component/BulkEditField';
|
||||
export const BulkEditPluginProvider = (props: any) => {
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
CustomizeBulkEditActionInitializer,
|
||||
CreateFormBulkEditBlockInitializer,
|
||||
BulkEditSubmitActionInitializer,
|
||||
BulkEditField,
|
||||
}}
|
||||
scope={{ useCustomizeBulkEditActionProps }}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ActionInitializer } from './ActionInitializer';
|
||||
import { ActionInitializer } from '@nocobase/client';
|
||||
|
||||
export const BulkEditSubmitActionInitializer = (props) => {
|
||||
const schema = {
|
@ -5,11 +5,19 @@ import { merge, uid } from '@formily/shared';
|
||||
import { Checkbox, Select, Space } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFormBlockContext } from '../../block-provider';
|
||||
import { CollectionFieldProvider, useCollection, useCollectionField } from '../../collection-manager';
|
||||
import { useCompile, useComponent } from '../../schema-component';
|
||||
import { DeletedField } from './DeletedField';
|
||||
import {
|
||||
useFormBlockContext,
|
||||
CollectionFieldProvider,
|
||||
useCollection,
|
||||
useCollectionField,
|
||||
useCompile,
|
||||
useComponent,
|
||||
} from '@nocobase/client';
|
||||
|
||||
export const DeletedField = () => {
|
||||
const { t } = useTranslation();
|
||||
return <div style={{ color: '#ccc' }}>{t('The field has bee deleted')}</div>;
|
||||
};
|
||||
const InternalField: React.FC = (props) => {
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
@ -83,7 +91,7 @@ export const BulkEditField = (props: any) => {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField<Field>();
|
||||
const [type, setType] = useState<number>(BulkEditFormItemValueType.RemainsTheSame);
|
||||
const [type, setType] = useState<number>(BulkEditFormItemValueType.ChangedTo);
|
||||
const [value, setValue] = useState(null);
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema.name) || {};
|
||||
@ -135,15 +143,9 @@ export const BulkEditField = (props: any) => {
|
||||
<Select.Option value={BulkEditFormItemValueType.AddAttach}>{t('Add attach')}</Select.Option>
|
||||
)}
|
||||
</Select>
|
||||
{/* XXX: Not a best practice */}
|
||||
{[BulkEditFormItemValueType.ChangedTo, BulkEditFormItemValueType.AddAttach].includes(type) &&
|
||||
collectionField?.interface !== 'checkbox' && (
|
||||
<CollectionField {...props} value={value} onChange={valueChangeHandler} style={{ minWidth: 150 }} />
|
||||
// <SchemaComponent
|
||||
// schema={collectionSchema}
|
||||
// components={{ BulkEditCollectionField: CollectionField }}
|
||||
// onlyRenderProperties
|
||||
// />
|
||||
)}
|
||||
{[BulkEditFormItemValueType.ChangedTo, BulkEditFormItemValueType.AddAttach].includes(type) &&
|
||||
collectionField?.interface === 'checkbox' && <Checkbox checked={value} onChange={valueChangeHandler} />}
|
@ -2,6 +2,7 @@ import { Plugin, useCollection } from '@nocobase/client';
|
||||
import { BulkEditPluginProvider } from './BulkEditPluginProvider';
|
||||
import { BulkEditFormItemInitializers } from './BulkEditFormItemInitializers';
|
||||
import { CreateFormBulkEditBlockInitializers } from './CreateFormBulkEditBlockInitializers';
|
||||
import { BulkEditFormActionInitializers } from './BulkEditFormActionInitializers';
|
||||
import { bulkEditactionSettings } from './BulkEditAction.Settings';
|
||||
export class BulkEditPlugin extends Plugin {
|
||||
async load() {
|
||||
@ -9,6 +10,7 @@ export class BulkEditPlugin extends Plugin {
|
||||
this.app.schemaSettingsManager.add(bulkEditactionSettings);
|
||||
this.app.schemaInitializerManager.add(BulkEditFormItemInitializers);
|
||||
this.app.schemaInitializerManager.add(CreateFormBulkEditBlockInitializers);
|
||||
this.app.schemaInitializerManager.add(BulkEditFormActionInitializers);
|
||||
|
||||
const initializerData = {
|
||||
type: 'item',
|
||||
|
@ -1,59 +0,0 @@
|
||||
import {
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useRemoveGridFormItem,
|
||||
SchemaInitializerItemType,
|
||||
} from '@nocobase/client';
|
||||
import { useForm } from '@formily/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useCustomBulkEditFormItemInitializerFields = (options?: any) => {
|
||||
const { name, fields } = useCollection();
|
||||
const { getInterface, getCollection } = useCollectionManager();
|
||||
const form = useForm();
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
const remove = useRemoveGridFormItem();
|
||||
const filterFields = useMemo(
|
||||
() =>
|
||||
fields
|
||||
?.filter((field) => {
|
||||
return (
|
||||
field?.interface &&
|
||||
!field?.uiSchema?.['x-read-pretty'] &&
|
||||
field.interface !== 'snapshot' &&
|
||||
field.type !== 'sequence'
|
||||
);
|
||||
})
|
||||
.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
const schema = {
|
||||
type: 'string',
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': 'BulkEditField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': `${name}.${field.name}`,
|
||||
};
|
||||
return {
|
||||
name: field?.uiSchema?.title || field.name,
|
||||
type: 'item',
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
Component: 'CollectionFieldInitializer',
|
||||
remove: remove,
|
||||
schemaInitialize: (s) => {
|
||||
interfaceConfig?.schemaInitialize?.(s, {
|
||||
field,
|
||||
block,
|
||||
readPretty,
|
||||
targetCollection: getCollection(field.target),
|
||||
});
|
||||
},
|
||||
schema,
|
||||
} as SchemaInitializerItemType;
|
||||
}),
|
||||
[fields],
|
||||
);
|
||||
|
||||
return filterFields;
|
||||
};
|
@ -0,0 +1,159 @@
|
||||
import {
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useRemoveGridFormItem,
|
||||
SchemaInitializerItemType,
|
||||
useBlockRequestContext,
|
||||
useActionContext,
|
||||
useCompile,
|
||||
useTableBlockContext,
|
||||
TableFieldResource,
|
||||
} from '@nocobase/client';
|
||||
import React, { useContext } from 'react';
|
||||
import { App, message } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SchemaExpressionScopeContext, useField, useForm } from '@formily/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { isURL } from '@nocobase/utils/client';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BulkEditFormItemValueType } from './component/BulkEditField';
|
||||
|
||||
export const useCustomBulkEditFormItemInitializerFields = (options?: any) => {
|
||||
const { name, fields } = useCollection();
|
||||
const { getInterface, getCollection } = useCollectionManager();
|
||||
const form = useForm();
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
const remove = useRemoveGridFormItem();
|
||||
const filterFields = useMemo(
|
||||
() =>
|
||||
fields
|
||||
?.filter((field) => {
|
||||
return (
|
||||
field?.interface &&
|
||||
!field?.uiSchema?.['x-read-pretty'] &&
|
||||
field.interface !== 'snapshot' &&
|
||||
field.type !== 'sequence'
|
||||
);
|
||||
})
|
||||
.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
const schema = {
|
||||
type: 'string',
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': 'BulkEditField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': `${name}.${field.name}`,
|
||||
};
|
||||
return {
|
||||
name: field?.uiSchema?.title || field.name,
|
||||
type: 'item',
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
Component: 'CollectionFieldInitializer',
|
||||
remove: remove,
|
||||
schemaInitialize: (s) => {
|
||||
interfaceConfig?.schemaInitialize?.(s, {
|
||||
field,
|
||||
block,
|
||||
readPretty,
|
||||
targetCollection: getCollection(field.target),
|
||||
});
|
||||
},
|
||||
schema,
|
||||
} as SchemaInitializerItemType;
|
||||
}),
|
||||
[fields],
|
||||
);
|
||||
|
||||
return filterFields;
|
||||
};
|
||||
|
||||
export const useCustomizeBulkEditActionProps = () => {
|
||||
const form = useForm();
|
||||
const { t } = useTranslation();
|
||||
const { field, resource, __parent } = useBlockRequestContext();
|
||||
const expressionScope = useContext(SchemaExpressionScopeContext);
|
||||
const actionContext = useActionContext();
|
||||
const navigate = useNavigate();
|
||||
const compile = useCompile();
|
||||
const actionField = useField();
|
||||
const tableBlockContext = useTableBlockContext();
|
||||
const { modal } = App.useApp();
|
||||
|
||||
const { rowKey } = tableBlockContext;
|
||||
const selectedRecordKeys =
|
||||
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
|
||||
const { setVisible, fieldSchema: actionSchema } = actionContext;
|
||||
return {
|
||||
async onClick() {
|
||||
const { onSuccess, skipValidator, updateMode } = actionSchema?.['x-action-settings'] ?? {};
|
||||
const { filter } = __parent.service.params?.[0] ?? {};
|
||||
if (!skipValidator) {
|
||||
await form.submit();
|
||||
}
|
||||
const values = cloneDeep(form.values);
|
||||
actionField.data = field.data || {};
|
||||
actionField.data.loading = true;
|
||||
for (const key in values) {
|
||||
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
||||
const value = values[key];
|
||||
if (BulkEditFormItemValueType.Clear in value) {
|
||||
values[key] = null;
|
||||
} else if (BulkEditFormItemValueType.ChangedTo in value) {
|
||||
values[key] = value[BulkEditFormItemValueType.ChangedTo];
|
||||
} else if (BulkEditFormItemValueType.RemainsTheSame in value) {
|
||||
delete values[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
const updateData: { filter?: any; values: any; forceUpdate: boolean } = {
|
||||
values,
|
||||
filter,
|
||||
forceUpdate: false,
|
||||
};
|
||||
if (updateMode === 'selected') {
|
||||
if (!selectedRecordKeys?.length) {
|
||||
message.error(t('Please select the records to be updated'));
|
||||
return;
|
||||
}
|
||||
updateData.filter = { $and: [{ [rowKey || 'id']: { $in: selectedRecordKeys } }] };
|
||||
}
|
||||
if (!updateData.filter) {
|
||||
updateData.forceUpdate = true;
|
||||
}
|
||||
await resource.update(updateData);
|
||||
actionField.data.loading = false;
|
||||
if (!(resource instanceof TableFieldResource)) {
|
||||
__parent?.__parent?.service?.refresh?.();
|
||||
}
|
||||
__parent?.service?.refresh?.();
|
||||
setVisible?.(false);
|
||||
if (!onSuccess?.successMessage) {
|
||||
return;
|
||||
}
|
||||
if (onSuccess?.manualClose) {
|
||||
modal.success({
|
||||
title: compile(onSuccess?.successMessage),
|
||||
onOk: async () => {
|
||||
await form.reset();
|
||||
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
|
||||
if (isURL(onSuccess.redirectTo)) {
|
||||
window.location.href = onSuccess.redirectTo;
|
||||
} else {
|
||||
navigate(onSuccess.redirectTo);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.success(compile(onSuccess?.successMessage));
|
||||
}
|
||||
} finally {
|
||||
actionField.data.loading = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
@ -33,7 +33,7 @@ export class CollectionRepository extends Repository {
|
||||
// set all graph nodes
|
||||
for (const instance of instances) {
|
||||
graph.setNode(instance.get('name'));
|
||||
if (instance.get('view')) {
|
||||
if (instance.get('view') || instance.get('sql')) {
|
||||
viewCollections.push(instance.get('name'));
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,233 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
CollectionTriggerNode,
|
||||
apiCreateWorkflow,
|
||||
apiDeleteWorkflow,
|
||||
apiGetWorkflow,
|
||||
apiUpdateWorkflowTrigger,
|
||||
appendJsonCollectionName,
|
||||
generalWithNoRelationalFields,
|
||||
ManualNode,
|
||||
} from '@nocobase/plugin-workflow-test/e2e';
|
||||
import { expect, test } from '@nocobase/test/e2e';
|
||||
import { dayjs } from '@nocobase/utils';
|
||||
|
||||
test('filter task node', async ({ page, mockPage, mockCollections, mockRecords }) => {
|
||||
//数据表后缀标识
|
||||
const triggerNodeAppendText = 'a' + faker.string.alphanumeric(4);
|
||||
const manualNodeAppendText = 'b' + dayjs().format('HHmmss').toString();
|
||||
|
||||
// 创建触发器节点数据表
|
||||
const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`;
|
||||
const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`;
|
||||
const triggerNodeFieldName = 'orgname';
|
||||
const triggerNodeFieldDisplayName = '公司名称(单行文本)';
|
||||
await mockCollections(
|
||||
appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText)
|
||||
.collections,
|
||||
);
|
||||
// 创建Manual节点数据表
|
||||
const manualNodeCollectionDisplayName = `自动>组织[普通表]${manualNodeAppendText}`;
|
||||
const manualNodeCollectionName = `tt_amt_org${manualNodeAppendText}`;
|
||||
const manualNodeFieldName = 'orgname';
|
||||
const manualNodeFieldDisplayName = '公司名称(单行文本)';
|
||||
await mockCollections(
|
||||
appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), manualNodeAppendText)
|
||||
.collections,
|
||||
);
|
||||
//添加工作流
|
||||
const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText;
|
||||
const workflowData = {
|
||||
current: true,
|
||||
options: { deleteExecutionOnStatus: [] },
|
||||
title: workFlowName,
|
||||
type: 'collection',
|
||||
enabled: true,
|
||||
};
|
||||
const workflow = await apiCreateWorkflow(workflowData);
|
||||
const workflowObj = JSON.parse(JSON.stringify(workflow));
|
||||
const workflowId = workflowObj.id;
|
||||
//配置工作流触发器
|
||||
const triggerNodeData = {
|
||||
config: { appends: [], collection: triggerNodeCollectionName, changed: [], condition: { $and: [] }, mode: 1 },
|
||||
};
|
||||
const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData);
|
||||
const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode));
|
||||
//配置Manual节点
|
||||
await page.goto(`admin/workflow/workflows/${workflowId}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName);
|
||||
await collectionTriggerNode.addNodeButton.click();
|
||||
await page.getByRole('button', { name: 'manual', exact: true }).click();
|
||||
const manualNodeName = 'Manual' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
await page.getByLabel('Manual-Manual', { exact: true }).getByRole('textbox').fill(manualNodeName);
|
||||
const manualNode = new ManualNode(page, manualNodeName);
|
||||
const manualNodeId = await manualNode.node.locator('.workflow-node-id').innerText();
|
||||
await manualNode.nodeConfigure.click();
|
||||
await manualNode.assigneesDropDown.click();
|
||||
await page.getByRole('option', { name: 'Super Admin' }).click();
|
||||
await manualNode.configureUserInterfaceButton.click();
|
||||
await manualNode.addBlockButton.hover();
|
||||
await manualNode.createRecordFormMenu.hover();
|
||||
await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page
|
||||
.locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
|
||||
.hover();
|
||||
await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit block title' }).click();
|
||||
const blockTitle = 'Create record' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await page
|
||||
.locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page.mouse.click(300, 0);
|
||||
await manualNode.submitButton.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 2、测试步骤:添加数据触发工作流
|
||||
const triggerNodeCollectionRecordOne = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
const triggerNodeCollectionRecords = await mockRecords(triggerNodeCollectionName, [
|
||||
{ orgname: triggerNodeCollectionRecordOne },
|
||||
]);
|
||||
await page.waitForTimeout(1000);
|
||||
// 3、预期结果:工作流成功触发,待办弹窗表单中显示数据
|
||||
const getWorkflow = await apiGetWorkflow(workflowId);
|
||||
const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow));
|
||||
const getWorkflowExecuted = getWorkflowObj.executed;
|
||||
expect(getWorkflowExecuted).toBe(1);
|
||||
|
||||
const newPage = mockPage();
|
||||
await newPage.goto();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
|
||||
await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page.waitForTimeout(300);
|
||||
await page.getByLabel('action-Filter.Action-Filter-filter-users_jobs-workflow-todo').click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Task right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Title' }).click();
|
||||
await page.getByRole('textbox').fill(manualNodeName);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// 3、预期结果:列表中出现筛选的工作流
|
||||
await expect(page.getByText(manualNodeName)).toBeAttached();
|
||||
|
||||
// 4、后置处理:删除工作流
|
||||
await apiDeleteWorkflow(workflowId);
|
||||
});
|
||||
|
||||
test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecords }) => {
|
||||
//数据表后缀标识
|
||||
const triggerNodeAppendText = 'a' + faker.string.alphanumeric(4);
|
||||
const manualNodeAppendText = 'b' + dayjs().format('HHmmss').toString();
|
||||
|
||||
// 创建触发器节点数据表
|
||||
const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`;
|
||||
const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`;
|
||||
const triggerNodeFieldName = 'orgname';
|
||||
const triggerNodeFieldDisplayName = '公司名称(单行文本)';
|
||||
await mockCollections(
|
||||
appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText)
|
||||
.collections,
|
||||
);
|
||||
// 创建Manual节点数据表
|
||||
const manualNodeCollectionDisplayName = `自动>组织[普通表]${manualNodeAppendText}`;
|
||||
const manualNodeCollectionName = `tt_amt_org${manualNodeAppendText}`;
|
||||
const manualNodeFieldName = 'orgname';
|
||||
const manualNodeFieldDisplayName = '公司名称(单行文本)';
|
||||
await mockCollections(
|
||||
appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), manualNodeAppendText)
|
||||
.collections,
|
||||
);
|
||||
//添加工作流
|
||||
const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText;
|
||||
const workflowData = {
|
||||
current: true,
|
||||
options: { deleteExecutionOnStatus: [] },
|
||||
title: workFlowName,
|
||||
type: 'collection',
|
||||
enabled: true,
|
||||
};
|
||||
const workflow = await apiCreateWorkflow(workflowData);
|
||||
const workflowObj = JSON.parse(JSON.stringify(workflow));
|
||||
const workflowId = workflowObj.id;
|
||||
//配置工作流触发器
|
||||
const triggerNodeData = {
|
||||
config: { appends: [], collection: triggerNodeCollectionName, changed: [], condition: { $and: [] }, mode: 1 },
|
||||
};
|
||||
const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData);
|
||||
const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode));
|
||||
//配置Manual节点
|
||||
await page.goto(`admin/workflow/workflows/${workflowId}`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName);
|
||||
await collectionTriggerNode.addNodeButton.click();
|
||||
await page.getByRole('button', { name: 'manual', exact: true }).click();
|
||||
const manualNodeName = 'Manual' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
await page.getByLabel('Manual-Manual', { exact: true }).getByRole('textbox').fill(manualNodeName);
|
||||
const manualNode = new ManualNode(page, manualNodeName);
|
||||
const manualNodeId = await manualNode.node.locator('.workflow-node-id').innerText();
|
||||
await manualNode.nodeConfigure.click();
|
||||
await manualNode.assigneesDropDown.click();
|
||||
await page.getByRole('option', { name: 'Super Admin' }).click();
|
||||
await manualNode.configureUserInterfaceButton.click();
|
||||
await manualNode.addBlockButton.hover();
|
||||
await manualNode.createRecordFormMenu.hover();
|
||||
await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page
|
||||
.locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
|
||||
.hover();
|
||||
await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit block title' }).click();
|
||||
const blockTitle = 'Create record' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await page
|
||||
.locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
|
||||
.hover();
|
||||
await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page.mouse.click(300, 0);
|
||||
await manualNode.submitButton.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// 2、测试步骤:添加数据触发工作流
|
||||
const triggerNodeCollectionRecordOne = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
|
||||
const triggerNodeCollectionRecords = await mockRecords(triggerNodeCollectionName, [
|
||||
{ orgname: triggerNodeCollectionRecordOne },
|
||||
]);
|
||||
await page.waitForTimeout(1000);
|
||||
// 3、预期结果:工作流成功触发,待办弹窗表单中显示数据
|
||||
const getWorkflow = await apiGetWorkflow(workflowId);
|
||||
const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow));
|
||||
const getWorkflowExecuted = getWorkflowObj.executed;
|
||||
expect(getWorkflowExecuted).toBe(1);
|
||||
|
||||
const newPage = mockPage();
|
||||
await newPage.goto();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
|
||||
await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
|
||||
await page.mouse.move(300, 0, { steps: 100 });
|
||||
await page.waitForTimeout(300);
|
||||
await page.getByLabel('action-Filter.Action-Filter-filter-users_jobs-workflow-todo').click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Workflow right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Name' }).click();
|
||||
await page.getByRole('textbox').fill(workFlowName);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// 3、预期结果:列表中出现筛选的工作流
|
||||
await expect(page.getByText(manualNodeName)).toBeAttached();
|
||||
|
||||
// 4、后置处理:删除工作流
|
||||
await apiDeleteWorkflow(workflowId);
|
||||
});
|
@ -435,11 +435,11 @@ export class ManualNode {
|
||||
this.assigneesDropDown = page.getByTestId('select-single');
|
||||
this.configureUserInterfaceButton = page.getByRole('button', { name: 'Configure user interface' });
|
||||
this.addBlockButton = page.getByLabel('schema-initializer-Grid-AddBlockButton-workflows');
|
||||
this.triggerDataMenu = page.getByLabel('Data blocks-triggerData');
|
||||
this.nodeDataMenu = page.getByLabel('nodes', { exact: true });
|
||||
this.triggerDataMenu = page.getByRole('menuitem', { name: 'Trigger data' });
|
||||
this.nodeDataMenu = page.getByRole('menuitem', { name: 'Node result right' });
|
||||
this.customFormMenu = page.getByRole('menuitem', { name: 'Custom form' });
|
||||
this.createRecordFormMenu = page.getByRole('menuitem', { name: 'Create record form' });
|
||||
this.updateRecordFormMenu = page.getByRole('menuitem', { name: 'Update record form' });
|
||||
this.createRecordFormMenu = page.getByRole('menuitem', { name: 'Create record form right' });
|
||||
this.updateRecordFormMenu = page.getByRole('menuitem', { name: 'Update record form right' });
|
||||
this.submitButton = page.getByLabel('action-Action-Submit-workflows');
|
||||
this.cancelButton = page.getByLabel('action-Action-Cancel-workflows');
|
||||
this.addNodeButton = page.getByLabel(`add-button-manual-${nodeName}`, { exact: true });
|
||||
|
@ -605,6 +605,44 @@ export const apiGetList = async (collectionName: string) => {
|
||||
return await result.json();
|
||||
};
|
||||
|
||||
// 查询业务表list
|
||||
export const apiFilterList = async (collectionName: string, filter: string) => {
|
||||
const api = await request.newContext({
|
||||
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
||||
});
|
||||
const state = await api.storageState();
|
||||
const headers = getHeaders(state);
|
||||
const result = await api.get(`/api/${collectionName}:list?${filter}`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!result.ok()) {
|
||||
throw new Error(await result.text());
|
||||
}
|
||||
/*
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"createdAt": "2023-12-12T02:43:53.793Z",
|
||||
"updatedAt": "2023-12-12T05:41:33.300Z",
|
||||
"key": "fzk3j2oj4el",
|
||||
"title": "a11",
|
||||
"enabled": true,
|
||||
"description": null
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"count": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPage": 1
|
||||
}
|
||||
}
|
||||
*/
|
||||
return await result.json();
|
||||
};
|
||||
|
||||
// 添加业务表单条数据触发工作流表单事件,triggerWorkflows=key1!field,key2,key3!field.subfield
|
||||
export const apiCreateRecordTriggerFormEvent = async (collectionName: string, triggerWorkflows: string, data: any) => {
|
||||
const api = await request.newContext({
|
||||
@ -744,4 +782,5 @@ export default module.exports = {
|
||||
apiGetList,
|
||||
apiCreateRecordTriggerFormEvent,
|
||||
apiSubmitRecordTriggerFormEvent,
|
||||
apiFilterList,
|
||||
};
|
||||
|
@ -200,8 +200,7 @@ const useStyles = createStyles(({ css, token }) => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 0;
|
||||
height: 4em;
|
||||
padding: 1em 0;
|
||||
height: 6em;
|
||||
border-left: 1px dashed ${token.colorBgLayout};
|
||||
|
||||
.anticon {
|
||||
|
@ -35,12 +35,20 @@ describe('workflow > instructions > end', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const n2 = await workflow.createNode({
|
||||
type: 'echo',
|
||||
upstreamId: n1.id,
|
||||
});
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
await plugin.trigger(workflow, {});
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.status).toBe(JOB_STATUS.RESOLVED);
|
||||
const jobs = await execution.getJobs();
|
||||
expect(jobs.length).toBe(1);
|
||||
expect(jobs[0].status).toBe(JOB_STATUS.RESOLVED);
|
||||
});
|
||||
|
||||
it('failed', async () => {
|
||||
@ -51,12 +59,20 @@ describe('workflow > instructions > end', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const n2 = await workflow.createNode({
|
||||
type: 'echo',
|
||||
upstreamId: n1.id,
|
||||
});
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
await plugin.trigger(workflow, {});
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toBe(EXECUTION_STATUS.FAILED);
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.status).toBe(JOB_STATUS.FAILED);
|
||||
const jobs = await execution.getJobs();
|
||||
expect(jobs.length).toBe(1);
|
||||
expect(jobs[0].status).toBe(JOB_STATUS.FAILED);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,9 +9,14 @@ interface Config {
|
||||
|
||||
export default class extends Instruction {
|
||||
async run(node: FlowNodeModel, prevJob, processor: Processor) {
|
||||
const { endStatus } = <Config>node.config;
|
||||
return {
|
||||
status: endStatus ?? JOB_STATUS.RESOLVED,
|
||||
};
|
||||
const { endStatus = JOB_STATUS.RESOLVED } = <Config>node.config;
|
||||
await processor.saveJob({
|
||||
status: endStatus,
|
||||
nodeId: node.id,
|
||||
nodeKey: node.key,
|
||||
upstreamId: prevJob?.id ?? null,
|
||||
});
|
||||
|
||||
return processor.exit(endStatus);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user