+
{props.addonBefore}
{props.prefix}
- {compile(props.value)}
+
+ {props.value && typeof props.value === 'object' ? JSON.stringify(props.value) : compile(props.value)}
+
{props.suffix}
{props.addonAfter}
diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
index 3420aa46a2..4101b58393 100644
--- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
+++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { DeleteOutlined, MenuOutlined } from '@ant-design/icons';
+import { DeleteOutlined, MenuOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { TinyColor } from '@ctrl/tinycolor';
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
import { css } from '@emotion/css';
@@ -261,21 +261,42 @@ const TableIndex = (props) => {
const usePaginationProps = (pagination1, pagination2) => {
const { t } = useTranslation();
+ const field: any = useField();
const pagination = useMemo(
() => ({ ...pagination1, ...pagination2 }),
[JSON.stringify({ ...pagination1, ...pagination2 })],
);
-
- const showTotal = useCallback((total) => t('Total {{count}} items', { count: total }), [t]);
-
- const result = useMemo(
- () => ({
- showTotal,
- showSizeChanger: true,
- ...pagination,
- }),
- [pagination, t, showTotal],
+ const { total: totalCount, current, pageSize } = pagination || {};
+ const showTotal = useCallback(
+ (total) => {
+ return t('Total {{count}} items', { count: total });
+ },
+ [t, totalCount],
);
+ const result = useMemo(() => {
+ if (totalCount) {
+ return {
+ showTotal,
+ showSizeChanger: true,
+ ...pagination,
+ };
+ } else {
+ return {
+ showTotal: false,
+ simple: { readOnly: true },
+ showTitle: false,
+ showSizeChanger: true,
+ hideOnSinglePage: false,
+ ...pagination,
+ total: field.value?.length < pageSize ? pageSize * current : pageSize * current + 1,
+ className: css`
+ .ant-pagination-simple-pager {
+ display: none !important;
+ }
+ `,
+ };
+ }
+ }, [pagination, t, showTotal]);
if (pagination2 === false) {
return false;
@@ -499,12 +520,12 @@ export const Table: any = withDynamicSchemaProps(
[rowKey, defaultRowKey],
);
- const dataSourceKeys = field?.value?.map(getRowKey);
+ const dataSourceKeys = field?.value?.map?.(getRowKey);
const memoizedDataSourceKeys = useMemo(() => dataSourceKeys, [JSON.stringify(dataSourceKeys)]);
- const dataSource = useMemo(
- () => [...(field?.value || [])].filter(Boolean),
- [field?.value, field?.value?.length, memoizedDataSourceKeys],
- );
+ const dataSource = useMemo(() => {
+ const value = Array.isArray(field?.value) ? field.value : [];
+ return value.filter(Boolean);
+ }, [field?.value, field?.value?.length, memoizedDataSourceKeys]);
const bodyWrapperComponent = useMemo(() => {
return (props) => {
diff --git a/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx b/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx
index c217871d48..258a1ef51c 100644
--- a/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx
+++ b/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx
@@ -10,8 +10,6 @@
import React, { useRef, useState } from 'react';
import { css } from '@emotion/css';
import { Button, Input } from 'antd';
-import { cloneDeep } from 'lodash';
-
import { VariableSelect } from './VariableSelect';
// NOTE: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js/46012210#46012210
@@ -27,9 +25,9 @@ function setNativeInputValue(input, value) {
export function RawTextArea(props): JSX.Element {
const inputRef = useRef
(null);
- const { changeOnSelect, component: Component = Input.TextArea, ...others } = props;
- const scope = typeof props.scope === 'function' ? props.scope() : props.scope;
- const [options, setOptions] = useState(scope ? scope : []);
+ const { changeOnSelect, component: Component = Input.TextArea, fieldNames, scope, ...others } = props;
+ const dataScope = typeof scope === 'function' ? scope() : scope;
+ const [options, setOptions] = useState(dataScope ? dataScope : []);
function onInsert(selected) {
if (!inputRef.current) {
@@ -77,7 +75,7 @@ export function RawTextArea(props): JSX.Element {
background-color: transparent;
`
}
- fieldNames={props.fieldNames}
+ fieldNames={fieldNames}
options={options}
setOptions={setOptions}
onInsert={onInsert}
diff --git a/packages/core/client/src/schema-component/types.ts b/packages/core/client/src/schema-component/types.ts
index a203604b18..ccd17e3757 100644
--- a/packages/core/client/src/schema-component/types.ts
+++ b/packages/core/client/src/schema-component/types.ts
@@ -20,6 +20,7 @@ export interface ISchemaComponentContext {
setDesignable?: (value: boolean) => void;
SchemaField?: React.FC;
distributed?: boolean;
+ [key: string]: any;
}
export interface ISchemaComponentProvider {
diff --git a/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx
index ffe6211a97..c56c7e4042 100644
--- a/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx
+++ b/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx
@@ -22,6 +22,7 @@ import {
useCreateEditFormBlock,
useCreateFormBlock,
useCreateTableBlock,
+ useActionAvailable,
} from '../..';
import { CompatibleSchemaInitializer } from '../../application/schema-initializer/CompatibleSchemaInitializer';
import { useCreateDetailsBlock } from '../../modules/blocks/data-blocks/details-multi/DetailsBlockInitializer';
@@ -145,9 +146,7 @@ function useRecordBlocks() {
showAssociationFields: true,
};
},
- useVisible() {
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('update'),
},
{
name: 'createForm',
diff --git a/packages/core/client/src/schema-settings/setDefaultSortingRulesSchemaSettingsItem.tsx b/packages/core/client/src/schema-settings/setDefaultSortingRulesSchemaSettingsItem.tsx
index a870bfc627..fb6952c4d6 100644
--- a/packages/core/client/src/schema-settings/setDefaultSortingRulesSchemaSettingsItem.tsx
+++ b/packages/core/client/src/schema-settings/setDefaultSortingRulesSchemaSettingsItem.tsx
@@ -15,6 +15,7 @@ import { useTableBlockContext } from '../block-provider';
import { useCollection_deprecated, useSortFields } from '../collection-manager';
import { useDesignable } from '../schema-component';
import { SchemaSettingsItemType } from '../application';
+import { useCollection } from '../data-source';
export const setDefaultSortingRulesSchemaSettingsItem: SchemaSettingsItemType = {
name: 'SetDefaultSortingRules',
@@ -134,7 +135,6 @@ export const setDefaultSortingRulesSchemaSettingsItem: SchemaSettingsItemType =
useVisible() {
const field = useField();
const { dragSort } = field.decoratorProps;
-
return !dragSort;
},
};
diff --git a/packages/core/client/src/schema-settings/setTheDataScopeSchemaSettingsItem.tsx b/packages/core/client/src/schema-settings/setTheDataScopeSchemaSettingsItem.tsx
index 936954b2b8..5db1aefc62 100644
--- a/packages/core/client/src/schema-settings/setTheDataScopeSchemaSettingsItem.tsx
+++ b/packages/core/client/src/schema-settings/setTheDataScopeSchemaSettingsItem.tsx
@@ -14,6 +14,7 @@ import { useFormBlockContext, useTableBlockContext } from '../block-provider';
import { useCollection_deprecated } from '../collection-manager';
import { useDesignable, removeNullCondition } from '../schema-component';
import { SchemaSettingsDataScope } from './SchemaSettingsDataScope';
+import { useCollection } from '../data-source';
export const setTheDataScopeSchemaSettingsItem: SchemaSettingsItemType = {
name: 'SetTheDataScope',
diff --git a/packages/core/data-source-manager/src/__tests__/collection-manager.test.ts b/packages/core/data-source-manager/src/__tests__/collection-manager.test.ts
index 6e57e01454..2ed2327660 100644
--- a/packages/core/data-source-manager/src/__tests__/collection-manager.test.ts
+++ b/packages/core/data-source-manager/src/__tests__/collection-manager.test.ts
@@ -83,6 +83,6 @@ describe('Collection Manager', () => {
const UsersCollection = collectionManager.getCollection('users');
- expect(UsersCollection.repository).toBe(MockRepository);
+ expect(UsersCollection.repository).toBeInstanceOf(MockRepository);
});
});
diff --git a/packages/core/data-source-manager/src/collection-manager.ts b/packages/core/data-source-manager/src/collection-manager.ts
index 7f29a55178..30d39b8471 100644
--- a/packages/core/data-source-manager/src/collection-manager.ts
+++ b/packages/core/data-source-manager/src/collection-manager.ts
@@ -16,13 +16,24 @@ import {
IRepository,
MergeOptions,
} from './types';
+import { DataSource } from './data-source';
+import { Repository } from './repository';
export class CollectionManager implements ICollectionManager {
+ public dataSource: DataSource;
protected collections = new Map();
protected repositories = new Map();
protected models = new Map();
- constructor(options = {}) {}
+ constructor(options: any = {}) {
+ if (options.dataSource) {
+ this.dataSource = options.dataSource;
+ }
+
+ this.registerRepositories({
+ Repository: Repository,
+ });
+ }
/* istanbul ignore next -- @preserve */
getRegisteredFieldType(type) {}
@@ -103,6 +114,10 @@ export class CollectionManager implements ICollectionManager {
async sync() {}
+ removeCollection(name: string): void {
+ this.collections.delete(name);
+ }
+
protected newCollection(options): ICollection {
// @ts-ignore
return new Collection(options, this);
diff --git a/packages/core/data-source-manager/src/collection.ts b/packages/core/data-source-manager/src/collection.ts
index 01c6615283..51e633fd17 100644
--- a/packages/core/data-source-manager/src/collection.ts
+++ b/packages/core/data-source-manager/src/collection.ts
@@ -9,29 +9,30 @@
import { CollectionOptions, ICollection, ICollectionManager, IField, IRepository } from './types';
import { default as lodash } from 'lodash';
-import merge from 'deepmerge';
import { CollectionField } from './collection-field';
export class Collection implements ICollection {
repository: IRepository;
fields: Map = new Map();
- constructor(
- protected options: CollectionOptions,
- protected collectionManager: ICollectionManager,
- ) {
+ constructor(protected options: CollectionOptions, protected collectionManager: ICollectionManager) {
this.setRepository(options.repository);
+
if (options.fields) {
this.setFields(options.fields);
}
}
updateOptions(options: CollectionOptions, mergeOptions?: any) {
- let newOptions = lodash.cloneDeep(options);
- newOptions = merge(this.options, newOptions, mergeOptions);
+ const newOptions = {
+ ...this.options,
+ ...lodash.cloneDeep(options),
+ };
+
this.options = newOptions;
this.setFields(newOptions.fields || []);
+
if (options.repository) {
this.setRepository(options.repository);
}
@@ -40,6 +41,11 @@ export class Collection implements ICollection {
}
setFields(fields: any[]) {
+ const fieldNames = this.fields.keys();
+ for (const fieldName of fieldNames) {
+ this.removeField(fieldName);
+ }
+
for (const field of fields) {
this.setField(field.name, field);
}
@@ -64,6 +70,7 @@ export class Collection implements ICollection {
}
protected setRepository(repository: any) {
- this.repository = this.collectionManager.getRegisteredRepository(repository || 'Repository');
+ const RepositoryClass = this.collectionManager.getRegisteredRepository(repository || 'Repository');
+ this.repository = new RepositoryClass(this);
}
}
diff --git a/packages/core/data-source-manager/src/data-source.ts b/packages/core/data-source-manager/src/data-source.ts
index b6762cc0a5..84ce8bdb29 100644
--- a/packages/core/data-source-manager/src/data-source.ts
+++ b/packages/core/data-source-manager/src/data-source.ts
@@ -87,6 +87,10 @@ export abstract class DataSource extends EventEmitter {
return new ResourceManager(options);
}
+ publicOptions() {
+ return null;
+ }
+
async load(options: any = {}) {}
async close() {}
diff --git a/packages/core/data-source-manager/src/index.ts b/packages/core/data-source-manager/src/index.ts
index 0b9abf563a..defaf540f8 100644
--- a/packages/core/data-source-manager/src/index.ts
+++ b/packages/core/data-source-manager/src/index.ts
@@ -8,11 +8,11 @@
*/
export * from './collection-manager';
+export * from './collection';
export * from './data-source';
export * from './data-source-manager';
export * from './sequelize-collection-manager';
export * from './sequelize-data-source';
-
export * from './load-default-actions';
export * from './types';
diff --git a/packages/core/data-source-manager/src/sequelize-collection-manager.ts b/packages/core/data-source-manager/src/sequelize-collection-manager.ts
index eff1e5390b..26537716f0 100644
--- a/packages/core/data-source-manager/src/sequelize-collection-manager.ts
+++ b/packages/core/data-source-manager/src/sequelize-collection-manager.ts
@@ -91,6 +91,8 @@ export class SequelizeCollectionManager implements ICollectionManager {
return this.db.getCollection(name);
}
+ removeCollection(name: string) {}
+
getCollections() {
const collectionsFilter = this.collectionsFilter();
diff --git a/packages/core/data-source-manager/src/types.ts b/packages/core/data-source-manager/src/types.ts
index f9a6364fcc..f5bf5da697 100644
--- a/packages/core/data-source-manager/src/types.ts
+++ b/packages/core/data-source-manager/src/types.ts
@@ -10,6 +10,7 @@
export type CollectionOptions = {
name: string;
repository?: string;
+ filterTargetKey?: string;
fields: any[];
[key: string]: any;
};
@@ -24,8 +25,8 @@ export type FieldOptions = {
uiSchema?: any;
possibleTypes?: string[];
defaultValue?: any;
- primaryKey: boolean;
- unique: boolean;
+ primaryKey?: boolean;
+ unique?: boolean;
allowNull?: boolean;
autoIncrement?: boolean;
[key: string]: any;
@@ -33,6 +34,7 @@ export type FieldOptions = {
export interface IField {
options: FieldOptions;
+
isRelationField(): boolean;
}
@@ -44,6 +46,7 @@ export interface IFieldInterface {
options: FieldOptions;
toString(value: any, ctx?: any): string;
+
toValue(str: string, ctx?: any): any;
}
@@ -63,6 +66,9 @@ export interface ICollection {
getField(name: string): IField;
[key: string]: any;
+
+ unavailableActions?: () => string[];
+ availableActions?: () => string[];
}
export interface IModel {
@@ -106,9 +112,9 @@ export interface ICollectionManager {
registerModels(models: Record): void;
- registerRepositories(repositories: Record): void;
+ registerRepositories(repositories: Record IRepository>): void;
- getRegisteredRepository(key: string): IRepository;
+ getRegisteredRepository(key: string): new (collection: ICollection) => IRepository;
defineCollection(options: CollectionOptions): ICollection;
@@ -120,6 +126,8 @@ export interface ICollectionManager {
getCollections(): Array;
+ removeCollection(name: string): void;
+
getRepository(name: string, sourceId?: string | number): IRepository;
sync(): Promise;
diff --git a/packages/core/database/src/__tests__/filter-match.test.ts b/packages/core/database/src/__tests__/filter-match.test.ts
index 3d2097cc34..f2f4b58362 100644
--- a/packages/core/database/src/__tests__/filter-match.test.ts
+++ b/packages/core/database/src/__tests__/filter-match.test.ts
@@ -61,4 +61,38 @@ describe('filterMatch', () => {
}),
).toBeFalsy();
});
+
+ test('filter by array operation', () => {
+ expect(
+ expect(
+ filterMatch(
+ {
+ tags: ['tag1', 'tag2'],
+ },
+ {
+ tags: {
+ $match: 'tag1',
+ },
+ },
+ ),
+ ).toBeTruthy(),
+ );
+ });
+
+ test('filter by date operation', () => {
+ expect(
+ expect(
+ filterMatch(
+ {
+ createdAt: '2013-02-08T09:30:26.123Z',
+ },
+ {
+ createdAt: {
+ $dateOn: '2013-02-08',
+ },
+ },
+ ),
+ ).toBeTruthy(),
+ );
+ });
});
diff --git a/packages/core/database/src/__tests__/magic-attribute-model.test.ts b/packages/core/database/src/__tests__/magic-attribute-model.test.ts
index 78592ed796..696c595f0e 100644
--- a/packages/core/database/src/__tests__/magic-attribute-model.test.ts
+++ b/packages/core/database/src/__tests__/magic-attribute-model.test.ts
@@ -22,6 +22,51 @@ describe('magic-attribute-model', () => {
await db.close();
});
+ it.skip('should update with magic attribute', async () => {
+ db.registerModels({ MagicAttributeModel });
+
+ const Test = db.collection({
+ name: 'tests',
+ model: 'MagicAttributeModel',
+ fields: [
+ { type: 'string', name: 'title' },
+ { type: 'json', name: 'options' },
+ ],
+ });
+
+ await db.sync();
+ const record0 = await Test.repository.create({
+ values: {
+ title: 'xxx',
+ other: 'a',
+ actions: {
+ list: {
+ a: 'b',
+ c: 'd',
+ },
+ get: {
+ a: 'b',
+ c: 'd',
+ },
+ },
+ },
+ });
+
+ await record0.update({
+ title: 'xxx',
+ other: 'b',
+ actions: {
+ list: {
+ a: 'b',
+ c: 'd',
+ },
+ },
+ });
+
+ const data = record0.toJSON();
+ expect(data['actions']['get']).toBeUndefined();
+ });
+
it('case 0', async () => {
db.registerModels({ MagicAttributeModel });
diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts
index 688b5c8a18..8b22f153ca 100644
--- a/packages/core/database/src/collection.ts
+++ b/packages/core/database/src/collection.ts
@@ -839,6 +839,14 @@ export class Collection<
return false;
}
+ unavailableActions() {
+ if (this.options.template === 'file') {
+ return ['create', 'update', 'destroy'];
+ }
+
+ return [];
+ }
+
protected sequelizeModelOptions() {
const { name } = this.options;
return {
diff --git a/packages/core/database/src/filter-match.ts b/packages/core/database/src/filter-match.ts
index 816064b45f..258f0e27e4 100644
--- a/packages/core/database/src/filter-match.ts
+++ b/packages/core/database/src/filter-match.ts
@@ -7,17 +7,34 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { filter } from 'mathjs';
-
+import moment from 'moment';
export function filterMatch(model, where) {
- if (where.filter !== undefined) {
- where = filter;
- }
-
// Create an object that maps operator names to functions
const operatorFunctions = {
+ // string
$eq: (value, condition) => value === condition,
$not: (value, condition) => !filterMatch(model, condition),
+ $includes: (value, condition) => value.includes(condition),
+ $notIncludes: (value, condition) => !value.includes(condition),
+ $empty: (value) =>
+ value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0),
+ $notEmpty: (value) =>
+ (value !== null && value !== undefined && value !== '') || (Array.isArray(value) && value.length > 0),
+
+ // array
+ $match: (value, condition) => value.some((item) => filterMatch(item, condition)),
+ $notMatch: (value, condition) => !value.some((item) => filterMatch(item, condition)),
+ $anyOf: (value, condition) => value.some((item) => condition.includes(item)),
+ $noneOf: (value, condition) => !value.some((item) => condition.includes(item)),
+
+ // datetime
+ $dateOn: (value, condition) => moment(value).isSame(condition, 'day'),
+ $dateNotOn: (value, condition) => !moment(value).isSame(condition, 'day'),
+ $dateBefore: (value, condition) => moment(value).isBefore(condition, 'day'),
+ $dateAfter: (value, condition) => moment(value).isAfter(condition, 'day'),
+ $dateNotBefore: (value, condition) => !moment(value).isBefore(condition, 'day'),
+ $dateNotAfter: (value, condition) => !moment(value).isAfter(condition, 'day'),
+
$gt: (value, condition) => value > condition,
$gte: (value, condition) => value >= condition,
$lt: (value, condition) => value < condition,
diff --git a/packages/core/database/src/view-collection.ts b/packages/core/database/src/view-collection.ts
index 0e09c43b99..1654423f66 100644
--- a/packages/core/database/src/view-collection.ts
+++ b/packages/core/database/src/view-collection.ts
@@ -21,6 +21,14 @@ export class ViewCollection extends Collection {
return true;
}
+ unavailableActions(): Array {
+ if (this.options.writableView) {
+ return [];
+ }
+
+ return ['create', 'update', 'destroy'];
+ }
+
protected sequelizeModelOptions(): any {
const modelOptions = super.sequelizeModelOptions();
modelOptions.tableName = this.options.viewName || this.options.name;
diff --git a/packages/core/server/src/plugin-manager/plugin-manager.ts b/packages/core/server/src/plugin-manager/plugin-manager.ts
index 34f5f4001d..5fcf31a6e4 100644
--- a/packages/core/server/src/plugin-manager/plugin-manager.ts
+++ b/packages/core/server/src/plugin-manager/plugin-manager.ts
@@ -198,6 +198,7 @@ export class PluginManager {
if (typeof pluginName === 'string') {
const packageName = isPkg ? pluginName : await this.getPackageName(pluginName);
this.clearCache(packageName);
+
return await importModule(packageName);
} else {
return pluginName;
diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts
index 0189b23696..7f20a0f15b 100644
--- a/packages/core/utils/src/index.ts
+++ b/packages/core/utils/src/index.ts
@@ -36,3 +36,4 @@ export * from './url';
export * from './i18n';
export { dayjs, lodash };
+export { Schema } from '@formily/json-schema';
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
index bde1e87542..c1f52a22bb 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/index.tsx
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { Plugin, useCollection_deprecated } from '@nocobase/client';
+import { Plugin, useActionAvailable } from '@nocobase/client';
import { bulkEditActionSettings, deprecatedBulkEditActionSettings } from './BulkEditAction.Settings';
import { BulkEditActionInitializer } from './BulkEditActionInitializer';
import {
@@ -54,14 +54,7 @@ export class PluginActionBulkEditClient extends Plugin {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (
- (collection.template !== 'view' || collection?.writableView) &&
- collection.template !== 'file' &&
- collection.template !== 'sql'
- );
- },
+ useVisible: () => useActionAvailable('updateMany'),
};
this.app.schemaInitializerManager.addItem('table:configureActions', 'customize.bulkEdit', initializerData);
diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/index.tsx b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/index.tsx
index a73ed3c2d0..755dfbebfe 100644
--- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/index.tsx
+++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/client/index.tsx
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { Plugin, useCollection_deprecated } from '@nocobase/client';
+import { Plugin, useActionAvailable } from '@nocobase/client';
import { bulkUpdateActionSettings, deprecatedBulkUpdateActionSettings } from './BulkUpdateAction.Settings';
import { BulkUpdateActionInitializer } from './BulkUpdateActionInitializer';
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
@@ -24,14 +24,7 @@ export class PluginActionBulkUpdateClient extends Plugin {
title: '{{t("Bulk update")}}',
Component: BulkUpdateActionInitializer,
name: 'bulkUpdate',
- useVisible() {
- const collection = useCollection_deprecated();
- return (
- (collection.template !== 'view' || collection?.writableView) &&
- collection.template !== 'file' &&
- collection.template !== 'sql'
- );
- },
+ useVisible: () => useActionAvailable('updateMany'),
};
this.app.schemaInitializerManager.addItem('table:configureActions', 'customize.bulkUpdate', initializerData);
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
index eba297dc34..882b100e64 100644
--- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
+++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/index.ts
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { Plugin, useCollection_deprecated } from '@nocobase/client';
+import { Plugin, useActionAvailable } from '@nocobase/client';
import { DuplicateAction } from './DuplicateAction';
import { deprecatedDuplicateActionSettings, duplicateActionSettings } from './DuplicateAction.Settings';
import { DuplicateActionInitializer } from './DuplicateActionInitializer';
@@ -23,29 +23,6 @@ export class PluginActionDuplicateClient extends Plugin {
this.app.schemaSettingsManager.add(deprecatedDuplicateActionSettings);
this.app.schemaSettingsManager.add(duplicateActionSettings);
- const initializerData = {
- title: '{{t("Duplicate")}}',
- Component: 'DuplicateActionInitializer',
- schema: {
- 'x-component': 'Action',
- 'x-action': 'duplicate',
- 'x-toolbar': 'ActionSchemaToolbar',
- 'x-settings': 'actionSettings:duplicate',
- 'x-decorator': 'ACLActionProvider',
- 'x-component-props': {
- type: 'primary',
- },
- },
- useVisible() {
- const collection = useCollection_deprecated();
- return (
- (collection.template !== 'view' || collection?.writableView) &&
- collection.template !== 'file' &&
- collection.template !== 'sql'
- );
- },
- };
-
const initializerTableData = {
title: '{{t("Duplicate")}}',
Component: 'DuplicateActionInitializer',
@@ -59,14 +36,7 @@ export class PluginActionDuplicateClient extends Plugin {
type: 'primary',
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (
- (collection.template !== 'view' || collection?.writableView) &&
- collection.template !== 'file' &&
- collection.template !== 'sql'
- );
- },
+ useVisible: () => useActionAvailable('create'),
};
this.app.schemaInitializerManager.addItem('table:configureItemActions', 'actions.duplicate', initializerTableData);
diff --git a/packages/plugins/@nocobase/plugin-action-export/src/client/index.ts b/packages/plugins/@nocobase/plugin-action-export/src/client/index.ts
index 9dbd00b52b..aa14c0f8c0 100644
--- a/packages/plugins/@nocobase/plugin-action-export/src/client/index.ts
+++ b/packages/plugins/@nocobase/plugin-action-export/src/client/index.ts
@@ -11,7 +11,7 @@ export * from './ExportActionInitializer';
export * from './ExportDesigner';
export * from './ExportPluginProvider';
export * from './useExportAction';
-import { Plugin } from '@nocobase/client';
+import { Plugin, useCollection, useActionAvailable } from '@nocobase/client';
import { ExportPluginProvider } from './ExportPluginProvider';
import { exportActionSchemaSettings } from './schemaSettings';
@@ -29,6 +29,7 @@ export class PluginActionExportClient extends Plugin {
skipScopeCheck: true,
},
},
+ useVisible: () => useActionAvailable('export'),
};
const tableActionInitializers = this.app.schemaInitializerManager.get('table:configureActions');
diff --git a/packages/plugins/@nocobase/plugin-action-import/src/client/index.ts b/packages/plugins/@nocobase/plugin-action-import/src/client/index.ts
index d7c908f1c6..25a89e0419 100644
--- a/packages/plugins/@nocobase/plugin-action-import/src/client/index.ts
+++ b/packages/plugins/@nocobase/plugin-action-import/src/client/index.ts
@@ -15,7 +15,7 @@ export * from './ImportDesigner';
export * from './ImportPluginProvider';
export * from './useImportAction';
-import { Plugin, useCollection_deprecated } from '@nocobase/client';
+import { Plugin, useActionAvailable } from '@nocobase/client';
import { ImportPluginProvider } from './ImportPluginProvider';
import { importActionSchemaSettings } from './schemaSettings';
@@ -34,14 +34,7 @@ export class PluginActionImportClient extends Plugin {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (
- (collection.template !== 'view' || collection?.writableView) &&
- collection.template !== 'file' &&
- collection.template !== 'sql'
- );
- },
+ useVisible: () => useActionAvailable('import'),
};
const tableActionInitializers = this.app.schemaInitializerManager.get('table:configureActions');
diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarActionInitializers.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarActionInitializers.tsx
index 6bd779ec11..78dca46f84 100644
--- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarActionInitializers.tsx
+++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarActionInitializers.tsx
@@ -10,7 +10,7 @@
import {
CompatibleSchemaInitializer,
InitializerWithSwitch,
- useCollection_deprecated,
+ useActionAvailable,
useSchemaInitializerItem,
} from '@nocobase/client';
import React from 'react';
@@ -90,10 +90,7 @@ const commonOptions = {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('create'),
},
],
};
diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarFormActionInitializers.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarFormActionInitializers.tsx
index 48e1ff9998..dd99d48fa4 100644
--- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarFormActionInitializers.tsx
+++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/initializers/CalendarFormActionInitializers.tsx
@@ -7,7 +7,12 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { SchemaInitializer, SchemaInitializerItemType, useCollection_deprecated } from '@nocobase/client';
+import {
+ SchemaInitializer,
+ SchemaInitializerItemType,
+ useCollection_deprecated,
+ useActionAvailable,
+} from '@nocobase/client';
import { generateNTemplate } from '../../../locale';
export const deleteEventActionInitializer: SchemaInitializerItemType = {
@@ -52,10 +57,7 @@ export const CalendarFormActionInitializers = new SchemaInitializer({
type: 'primary',
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('update'),
},
{
name: 'delete',
@@ -65,10 +67,7 @@ export const CalendarFormActionInitializers = new SchemaInitializer({
'x-component': 'Action',
'x-decorator': 'ACLActionProvider',
},
- useVisible: function useVisible() {
- const collection = useCollection_deprecated();
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('destroy'),
},
deleteEventActionInitializer,
],
@@ -157,19 +156,12 @@ export const CalendarFormActionInitializers = new SchemaInitializer({
triggerWorkflows: [],
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('update'),
},
{
name: 'customRequest',
title: generateNTemplate('Custom request'),
Component: 'CustomRequestInitializer',
- useVisible() {
- const collection = useCollection_deprecated();
- return (collection.template !== 'view' || collection?.writableView) && collection.template !== 'sql';
- },
},
],
},
diff --git a/packages/plugins/@nocobase/plugin-collection-sql/src/server/sql-collection/sql-collection.ts b/packages/plugins/@nocobase/plugin-collection-sql/src/server/sql-collection/sql-collection.ts
index 9c6e807378..b5cb001d7c 100644
--- a/packages/plugins/@nocobase/plugin-collection-sql/src/server/sql-collection/sql-collection.ts
+++ b/packages/plugins/@nocobase/plugin-collection-sql/src/server/sql-collection/sql-collection.ts
@@ -24,6 +24,10 @@ export class SQLCollection extends Collection {
return true;
}
+ unavailableActions(): Array {
+ return ['create', 'update', 'destroy'];
+ }
+
public collectionSchema() {
return undefined;
}
diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts
index 317595a6f2..c4833ab77c 100644
--- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts
@@ -582,4 +582,13 @@ describe('collections repository', () => {
),
).not.toBeDefined();
});
+
+ it('should get collection list with unavailableActions', async () => {
+ const response = await app.agent().resource('collections').list();
+ expect(response.statusCode).toBe(200);
+ const data = response.body.data;
+ const firstCollection = data[0];
+ const collectionInMemory = app.db.getCollection(firstCollection.name);
+ expect(firstCollection.unavailableActions).toEqual(collectionInMemory.unavailableActions());
+ });
});
diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts
index f65c77f4fb..18efa02366 100644
--- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts
@@ -26,10 +26,23 @@ export class CollectionModel extends MagicAttributeModel {
toJSON() {
const json = super.toJSON();
+
+ const collection = this.db.getCollection(json.name);
+
if (!json.filterTargetKey) {
- const collection = this.db.getCollection(json.name);
json.filterTargetKey = collection?.filterTargetKey;
}
+
+ if (collection && collection.unavailableActions) {
+ json['unavailableActions'] = collection.unavailableActions();
+ }
+
+ // @ts-ignore
+ if (collection && collection.availableActions) {
+ // @ts-ignore
+ json['availableActions'] = collection.availableActions();
+ }
+
return json;
}
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx
index 57fac41ec4..832af9de63 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTable.tsx
@@ -10,7 +10,7 @@
import { useForm, useField } from '@formily/react';
import { action } from '@formily/reactive';
import { uid } from '@formily/shared';
-import React, { useContext, useMemo, useState } from 'react';
+import React, { useContext, useMemo, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import {
@@ -20,7 +20,7 @@ import {
SchemaComponent,
SchemaComponentContext,
useCompile,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
useCollectionManager_deprecated,
useCancelAction,
AddSubFieldAction,
@@ -33,7 +33,6 @@ import {
import { message } from 'antd';
import { getCollectionSchema } from './schema/collections';
import { CollectionFields } from './CollectionFields';
-import { EditCollection } from './EditCollectionAction';
import { DataSourceContext } from '../../DatabaseConnectionProvider';
/**
@@ -95,9 +94,8 @@ export const ConfigurationTable = () => {
const ds = useDataSourceManager();
const ctx = useContext(SchemaComponentContext);
const { name } = useParams();
- const data = useContext(CollectionCategroriesContext);
+ const data = useContext(CollectionCategoriesContext);
const api = useAPIClient();
- const resource = api.resource('dbViews');
const compile = useCompile();
const loadCategories = async () => {
return data.data.map((item: any) => ({
@@ -106,18 +104,6 @@ export const ConfigurationTable = () => {
}));
};
- const loadDBViews = async () => {
- return resource.list().then(({ data }) => {
- return data?.data?.map((item: any) => {
- const schema = item.schema;
- return {
- label: schema ? `${schema}.${compile(item.name)}` : item.name,
- value: schema ? `${schema}_${item.name}` : item.name,
- };
- });
- });
- };
-
const loadStorages = async () => {
return api
.resource('storages')
@@ -165,12 +151,43 @@ export const ConfigurationTable = () => {
const collectionSchema = useMemo(() => {
return getCollectionSchema(name);
}, [name]);
+
+ const resource = api.resource('dataSources', name);
+ const [dataSourceData, setDataSourceData] = useState({});
+
+ useEffect(() => {
+ try {
+ // eslint-disable-next-line promise/catch-or-return
+ resource
+ .get({
+ filterByTk: name,
+ })
+ .then((data) => {
+ setDataSourceData(data?.data);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ }, [name]);
+
+ const loadFilterTargetKeys = async (field) => {
+ const { fields } = field.form.values;
+ return Promise.resolve({
+ data: fields,
+ }).then(({ data }) => {
+ return data?.map((item: any) => {
+ return {
+ label: compile(item.uiSchema?.title) || item.name,
+ value: item.name,
+ };
+ });
+ });
+ };
return (
-
+
{
useBulkDestroySubField,
useSelectedRowKeys,
useAsyncDataSource,
+ loadFilterTargetKeys,
loadCategories,
- loadDBViews,
loadStorages,
useNewId,
useCancelAction,
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTabs.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTabs.tsx
index eb5526253f..d9201a9ca3 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTabs.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/ConfigurationTabs.tsx
@@ -18,7 +18,7 @@ import {
SchemaComponentOptions,
useCompile,
useResourceActionContext,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
} from '@nocobase/client';
import { CollectionFields } from './CollectionFields';
import { CollectionName } from './components/CollectionName';
@@ -37,7 +37,7 @@ const TabBar = ({ item }) => {
const DndProvider = observer(
(props) => {
const [activeTab, setActiveId] = useState(null);
- const { refresh } = useContext(CollectionCategroriesContext);
+ const { refresh } = useContext(CollectionCategoriesContext);
const { refresh: refreshCM } = useResourceActionContext();
const api = useAPIClient();
const onDragEnd = async (props: DragEndEvent) => {
@@ -78,7 +78,7 @@ const DndProvider = observer(
);
export const ConfigurationTabs = () => {
const { t } = useTranslation();
- const { data } = useContext(CollectionCategroriesContext);
+ const { data } = useContext(CollectionCategoriesContext);
const compile = useCompile();
if (!data) return null;
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionName.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionName.tsx
index 1499dc21b7..e22b5b56a9 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionName.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionName.tsx
@@ -22,7 +22,7 @@ const ReadPretty = (props) => {
} = useCollectionRecord() as any;
return (
- {name !== tableName ? (
+ {name !== tableName && tableName ? (
<>
{name}
({tableName})
>
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/index.tsx
index 992227d220..df322cc4f7 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/index.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/index.tsx
@@ -16,8 +16,6 @@ import {
AddCollectionAction,
AddCollectionField,
AddFieldAction,
- EditCollection,
- EditCollectionAction,
EditCollectionField,
EditFieldAction,
OverridingCollectionField,
@@ -29,10 +27,14 @@ import {
SyncSQLFieldsAction,
DeleteCollection,
DeleteCollectionAction,
- CollectionCategroriesProvider,
+ CollectionCategoriesProvider,
+ usePlugin,
} from '@nocobase/client';
+import { useLocation } from 'react-router-dom';
import { ConfigurationTable } from './ConfigurationTable';
import { ConfigurationTabs } from './ConfigurationTabs';
+import PluginDatabaseConnectionsClient from '../../';
+import { EditCollection } from './EditCollectionAction';
const schema2: ISchema = {
type: 'object',
@@ -44,20 +46,23 @@ const schema2: ISchema = {
};
export const CollectionManagerPage = () => {
+ const plugin = usePlugin(PluginDatabaseConnectionsClient);
+ const location = useLocation();
+ const dataSourceType = new URLSearchParams(location.search).get('type');
+ const type = dataSourceType && plugin.types.get(dataSourceType);
return (
{
SyncFieldsActionCom,
SyncSQLFieldsAction,
}}
+ scope={{
+ allowCollectionDeletion: !!type?.allowCollectionDeletion,
+ disabledConfigureFields: type?.disabledConfigureFields,
+ disableAddFields: type?.disableAddFields,
+ allowCollectionCreate: !!type?.allowCollectionCreate,
+ }}
/>
);
};
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collectionFields.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collectionFields.ts
index 682a4d981a..0d21b5d5bf 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collectionFields.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collectionFields.ts
@@ -125,6 +125,7 @@ export const fieldsTableSchema: ISchema = {
'x-component-props': {
type: 'primary',
},
+ 'x-hidden': '{{ disableAddFields }}',
},
},
},
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collections.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collections.ts
index fcc411cddd..14f1251d51 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collections.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/schema/collections.ts
@@ -169,7 +169,7 @@ export const collectionTableSchema: ISchema = {
role: 'button',
isBulk: true,
},
- 'x-visible': false,
+ 'x-visible': '{{allowCollectionDeletion}}',
},
create: {
type: 'void',
@@ -178,7 +178,7 @@ export const collectionTableSchema: ISchema = {
'x-component-props': {
type: 'primary',
},
- 'x-visible': false,
+ 'x-visible': '{{allowCollectionCreate}}',
},
},
},
@@ -299,6 +299,7 @@ export const collectionTableSchema: ISchema = {
},
},
},
+ 'x-hidden': '{{disabledConfigureFields}}',
},
update: {
type: 'void',
@@ -313,7 +314,7 @@ export const collectionTableSchema: ISchema = {
delete: {
type: 'void',
title: '{{ t("Delete") }}',
- 'x-visible': false,
+ 'x-visible': '{{allowCollectionDeletion}}',
'x-component': 'DeleteCollection',
'x-component-props': {
role: 'button',
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CreateDatabaseConnectAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CreateDatabaseConnectAction.tsx
index 9a5699b694..976152e2c8 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CreateDatabaseConnectAction.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CreateDatabaseConnectAction.tsx
@@ -57,6 +57,7 @@ export const CreateDatabaseConnectAction = () => {
'x-decorator-props': {
initialValue: {
type: info.key,
+ key: `d_${uid()}`,
},
},
title: compile("{{t('Add new')}}") + ' - ' + compile(type.label),
@@ -69,12 +70,13 @@ export const CreateDatabaseConnectAction = () => {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
- testConnectiion: {
+ testConnection: {
title: `{{ t("Test Connection",{ ns: "${NAMESPACE}" }) }}`,
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useTestConnectionAction }}',
},
+ 'x-hidden': type?.disableTestConnection,
},
cancel: {
title: '{{t("Cancel")}}',
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/EditDatabaseConnectionAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/EditDatabaseConnectionAction.tsx
index fd823a6d8f..7787737a18 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/EditDatabaseConnectionAction.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/EditDatabaseConnectionAction.tsx
@@ -103,6 +103,7 @@ export const EditDatabaseConnectionAction = () => {
'x-component-props': {
useAction: '{{ useTestConnectionAction }}',
},
+ 'x-hidden': type?.disableTestConnection,
},
submit: {
title: '{{t("Submit")}}',
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/AddCategoryAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/AddCategoryAction.tsx
index 7ec913b944..8fb5ea77e5 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/AddCategoryAction.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/AddCategoryAction.tsx
@@ -18,7 +18,7 @@ import {
SchemaComponent,
useActionContext,
useCancelAction,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
CollectionCategory,
CollectionTemplateTag,
} from '@nocobase/client';
@@ -27,7 +27,7 @@ import { collectionCategorySchema } from './schemas/collections';
const useCreateCategry = () => {
const form = useForm();
const ctx = useActionContext();
- const { refresh } = useContext(CollectionCategroriesContext);
+ const { refresh } = useContext(CollectionCategoriesContext);
const api = useAPIClient();
return {
async run() {
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx
index 6ed2d5f809..1d68668885 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTable.tsx
@@ -25,7 +25,7 @@ import {
DataSourceContext_deprecated,
AddSubFieldAction,
EditSubFieldAction,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
FieldSummary,
TemplateSummary,
useRequest,
@@ -106,7 +106,7 @@ export const ConfigurationTable = () => {
data: { database },
} = useCurrentAppInfo();
- const data = useContext(CollectionCategroriesContext);
+ const data = useContext(CollectionCategoriesContext);
const api = useAPIClient();
const resource = api.resource('dbViews');
const compile = useCompile();
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTabs.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTabs.tsx
index 97db883dbe..75335e9bb4 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTabs.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/ConfigurationTabs.tsx
@@ -30,7 +30,7 @@ import {
SchemaComponentOptions,
useCompile,
useResourceActionContext,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
} from '@nocobase/client';
import { CollectionFields } from './CollectionFields';
import { collectionTableSchema } from './schemas/collections';
@@ -93,7 +93,7 @@ const TabBar = ({ item }) => {
const DndProvider = observer(
(props) => {
const [activeTab, setActiveId] = useState(null);
- const { refresh } = useContext(CollectionCategroriesContext);
+ const { refresh } = useContext(CollectionCategoriesContext);
const { refresh: refreshCM } = useResourceActionContext();
const api = useAPIClient();
const onDragEnd = async (props: DragEndEvent) => {
@@ -134,7 +134,7 @@ const DndProvider = observer(
);
export const ConfigurationTabs = () => {
const { t } = useTranslation();
- const { data, refresh } = useContext(CollectionCategroriesContext);
+ const { data, refresh } = useContext(CollectionCategoriesContext);
const { refresh: refreshCM, run, defaultRequest, setState } = useResourceActionContext();
const [activeKey, setActiveKey] = useState({ tab: 'all' });
const [key, setKey] = useState(activeKey.tab);
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/EditCategoryAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/EditCategoryAction.tsx
index 8537143283..060d1f6e50 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/EditCategoryAction.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/Configuration/EditCategoryAction.tsx
@@ -21,14 +21,14 @@ import {
useCompile,
useResourceActionContext,
useCancelAction,
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
} from '@nocobase/client';
import { collectionCategoryEditSchema } from './schemas/collections';
const useEditCategry = () => {
const form = useForm();
const ctx = useActionContext();
- const { refresh } = useContext(CollectionCategroriesContext);
+ const { refresh } = useContext(CollectionCategoriesContext);
const { refresh: refreshCM } = useResourceActionContext();
const api = useAPIClient();
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/index.tsx
index 2ddfa6464c..6b9207ce7d 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/index.tsx
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/index.tsx
@@ -29,7 +29,7 @@ import {
SyncSQLFieldsAction,
DeleteCollection,
DeleteCollectionAction,
- CollectionCategroriesProvider,
+ CollectionCategoriesProvider,
} from '@nocobase/client';
import {
AddCategory,
@@ -55,7 +55,7 @@ export const MainDataSourceManager = () => {
{
style={{ padding: '0px' }}
disabled={!record.enabled}
onClick={() => {
- navigate(getConnectionCollectionPath(record.key));
+ navigate(getConnectionCollectionPath(record));
}}
role="button"
aria-label={`${record?.key}-Configure`}
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/constant.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/constant.ts
index 0dd1cfef08..c37046c5f7 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/constant.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/constant.ts
@@ -7,5 +7,6 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-export const getConnectionCollectionPath = (name: string | number) =>
- `/admin/settings/data-source-manager/${name}/collections`;
+export const getConnectionCollectionPath = ({ key, type }: { key: string | number; type: string }) => {
+ return `/admin/settings/data-source-manager/${key}/collections?type=${type}`;
+};
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-collections.test.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-collections.test.ts
new file mode 100644
index 0000000000..8d54da7a73
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-source-collections.test.ts
@@ -0,0 +1,142 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { createMockServer, MockServer, waitSecond } from '@nocobase/test';
+import { CollectionManager, DataSource } from '@nocobase/data-source-manager';
+import { HasManyRepository } from '@nocobase/database';
+
+describe('data source collection', () => {
+ let app: MockServer;
+
+ beforeEach(async () => {
+ app = await createMockServer({
+ plugins: ['nocobase', 'data-source-manager'],
+ });
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should update collection fields', async () => {
+ class MockDataSource extends DataSource {
+ static testConnection(options?: any): Promise {
+ return Promise.resolve(true);
+ }
+
+ async load(): Promise {
+ await waitSecond(1000);
+ }
+
+ createCollectionManager(options?: any): any {
+ return new CollectionManager(options);
+ }
+ }
+
+ app.dataSourceManager.factory.register('mock', MockDataSource);
+
+ await app.db.getRepository('dataSources').create({
+ values: {
+ key: 'mockInstance1',
+ type: 'mock',
+ displayName: 'Mock',
+ options: {},
+ },
+ });
+
+ await waitSecond(2000);
+
+ const dataSource = app.dataSourceManager.get('mockInstance1');
+ const collectionInDb = await app.db.getRepository('dataSourcesCollections').create({
+ values: {
+ name: 'test',
+ title: 'Test',
+ dataSourceKey: 'mockInstance1',
+ fields: [
+ {
+ name: 'name',
+ type: 'string',
+ },
+ {
+ name: 'age',
+ type: 'integer',
+ },
+ ],
+ },
+ updateAssociationValues: ['fields'],
+ });
+
+ // should get collection from collection manager
+ const collectionManager = dataSource.collectionManager;
+ const collection = collectionManager.getCollection('test');
+ expect(collection).toBeTruthy();
+ expect(collection.getField('name')).toBeTruthy();
+ expect(collection.getField('age')).toBeTruthy();
+
+ const collectionInJson = collectionInDb.toJSON();
+
+ // set name field ui schema
+ collectionInJson.fields.forEach((field: any) => {
+ if (field.name === 'name') {
+ field.uiSchema = {
+ title: 'Name',
+ 'x-component': 'Input',
+ };
+ }
+ });
+
+ // push a new fields
+ collectionInJson.fields.push({
+ name: 'email',
+ type: 'string',
+ });
+
+ // update collection with fields
+ await app.db.getRepository('dataSources.collections', 'mockInstance1').update({
+ filterByTk: 'test',
+ values: collectionInJson,
+ updateAssociationValues: ['fields'],
+ });
+
+ // get collection from collection manager again
+ const collection2 = collectionManager.getCollection('test');
+ expect(collection2).toBeTruthy();
+
+ expect(collection2.getField('name')).toBeTruthy();
+ expect(collection2.getField('age')).toBeTruthy();
+ expect(collection2.getField('email')).toBeTruthy();
+
+ expect(collection2.getField('name').options.uiSchema).toEqual({
+ title: 'Name',
+ 'x-component': 'Input',
+ });
+
+ const collectionInDb2 = await app.db.getRepository('dataSourcesCollections').findOne({
+ filter: {
+ name: 'test',
+ },
+ appends: ['fields'],
+ });
+
+ const collectionInJson2 = collectionInDb2.toJSON();
+ // it should remove field in update
+ collectionInJson2.fields = collectionInJson2.fields.filter((field: any) => field.name !== 'age');
+
+ await app.db.getRepository('dataSources.collections', 'mockInstance1').update({
+ filterByTk: 'test',
+ values: collectionInJson2,
+ updateAssociationValues: ['fields'],
+ });
+
+ const collection3 = collectionManager.getCollection('test');
+ expect(collection3).toBeTruthy();
+ expect(collection3.getField('name')).toBeTruthy();
+ expect(collection3.getField('age')).toBeFalsy();
+ });
+});
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts
index d51e95d77b..1812ab8d28 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts
@@ -400,7 +400,10 @@ describe('data source', async () => {
},
});
- await waitSecond(1000);
+ await waitSecond(2000);
+
+ const dataSource = app.dataSourceManager.dataSources.get('mockInstance1');
+ expect(dataSource).toBeDefined();
});
it('should get data source collections', async () => {
@@ -515,7 +518,53 @@ describe('data source', async () => {
expect(field.options.title).toBe('标题 Field');
});
- it('should create collection field', async () => {
+ it('should update fields through collection', async () => {
+ const dataSource = app.dataSourceManager.dataSources.get('mockInstance1');
+ const collection = dataSource.collectionManager.getCollection('posts');
+
+ const updateResp = await app
+ .agent()
+ .resource('dataSources.collections', 'mockInstance1')
+ .update({
+ filterByTk: 'posts',
+ values: {
+ fields: [
+ {
+ type: 'string',
+ name: 'title',
+ uiSchema: {
+ test: 'value',
+ },
+ },
+ {
+ type: 'text',
+ name: 'content',
+ },
+ ],
+ },
+ });
+
+ expect(updateResp.status).toBe(200);
+
+ const fieldsOptions = [...collection.fields.values()].map((f) => f.options);
+ // remove a field
+ const newFieldsOptions = fieldsOptions.filter((f) => f.name === 'title');
+
+ const updateResp2 = await app
+ .agent()
+ .resource('dataSources.collections', 'mockInstance1')
+ .update({
+ filterByTk: 'posts',
+ values: {
+ fields: newFieldsOptions,
+ },
+ });
+
+ expect(updateResp2.status).toBe(200);
+ expect(collection.getField('comments')).toBeFalsy();
+ });
+
+ it('should update collection with field', async () => {
const dataSource = app.dataSourceManager.dataSources.get('mockInstance1');
const collection = dataSource.collectionManager.getCollection('comments');
@@ -549,6 +598,20 @@ describe('data source', async () => {
expect(destroyResp.status).toBe(200);
expect(collection.getField('post')).toBeFalsy();
+
+ // reload data source manager
+ const refreshResp = await app.agent().resource('dataSources').refresh({
+ filterByTk: 'mockInstance1',
+ });
+
+ expect(refreshResp.status).toBe(200);
+ expect(refreshResp.body.data.status).toBe('reloading');
+
+ await waitSecond(2000);
+
+ const dataSource2 = app.dataSourceManager.dataSources.get('mockInstance1');
+ const collection2 = dataSource2.collectionManager.getCollection('comments');
+ expect(collection2.getField('post')).toBeFalsy();
});
});
});
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/collections/data-sources.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/collections/data-sources.ts
index 33abc24c6c..50aadf8154 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/collections/data-sources.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/collections/data-sources.ts
@@ -48,6 +48,7 @@ export default defineCollection({
name: 'collections',
target: 'dataSourcesCollections',
foreignKey: 'dataSourceKey',
+ targetKey: 'name',
},
{
type: 'hasMany',
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-collection-model.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-collection-model.ts
index c8b1a77085..a24f469091 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-collection-model.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-collection-model.ts
@@ -7,17 +7,39 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { MagicAttributeModel } from '@nocobase/database';
+import { MagicAttributeModel, Model } from '@nocobase/database';
import { Application } from '@nocobase/server';
+import { Transaction } from 'sequelize';
export class DataSourcesCollectionModel extends MagicAttributeModel {
- load(loadOptions: { app: Application }) {
- const { app } = loadOptions;
+ async load(loadOptions: { app: Application; transaction: Transaction }) {
+ const { app, transaction } = loadOptions;
+
+ const collectionFields = await this.getFields({ transaction });
const collectionOptions = this.get();
+ collectionOptions.fields = collectionFields;
+
const dataSourceName = this.get('dataSourceKey');
const dataSource = app.dataSourceManager.dataSources.get(dataSourceName);
const collection = dataSource.collectionManager.getCollection(collectionOptions.name);
- collection.updateOptions(collectionOptions);
+
+ if (collectionOptions.fields) {
+ collectionOptions.fields = collectionOptions.fields.map((field) => {
+ if (field instanceof Model) {
+ return field.get();
+ }
+
+ return field;
+ });
+ }
+
+ if (collection) {
+ collection.updateOptions(collectionOptions);
+ } else {
+ dataSource.collectionManager.defineCollection(collectionOptions);
+ }
+
+ return collection;
}
}
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts
index 3427ce48bf..c4b3ca74e3 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts
@@ -24,7 +24,6 @@ export class DataSourcesFieldModel extends MagicAttributeModel {
const dataSource = app.dataSourceManager.dataSources.get(dataSourceKey);
const collection = dataSource.collectionManager.getCollection(collectionName);
const oldField = collection.getField(name);
-
const newOptions = mergeOptions(oldField ? oldField.options : {}, options);
collection.setField(name, newOptions);
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts
index 0a86c9a987..8df2e0677f 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts
@@ -182,6 +182,11 @@ export class PluginDataSourceManagerServer extends Plugin {
isDBInstance: !!dataSource?.collectionManager.db,
};
+ const publicOptions = dataSource?.publicOptions();
+ if (publicOptions) {
+ item['options'] = publicOptions;
+ }
+
if (dataSourceStatus === 'loading-failed' || dataSourceStatus === 'reloading-failed') {
item['errorMessage'] = plugin.dataSourceErrors[dataSourceModel.get('key')].message;
}
@@ -195,12 +200,24 @@ export class PluginDataSourceManagerServer extends Plugin {
item.collections = collections.map((collection) => {
const collectionOptions = collection.options;
+ const collectionInstance = dataSource.collectionManager.getCollection(collectionOptions.name);
+
const fields = [...collection.fields.values()].map((field) => field.options);
- return {
+ const results = {
...collectionOptions,
fields,
};
+
+ if (collectionInstance && collectionInstance.availableActions) {
+ results['availableActions'] = collectionInstance.availableActions();
+ }
+
+ if (collectionInstance && collectionInstance.unavailableActions) {
+ results['unavailableActions'] = collectionInstance.unavailableActions();
+ }
+
+ return results;
});
}
@@ -320,7 +337,27 @@ export class PluginDataSourceManagerServer extends Plugin {
name: 'dataSources',
});
- this.app.db.on('dataSourcesFields.afterSave', async (model: DataSourcesFieldModel) => {
+ this.app.db.on('dataSourcesFields.beforeSave', async (model: DataSourcesFieldModel, options) => {
+ const { transaction } = options;
+ if (!model.get('collectionName') || !model.get('dataSourceKey')) {
+ const collectionKey = model.get('collectionKey');
+ if (!collectionKey) {
+ throw new Error('collectionKey is required');
+ }
+
+ const collection = await model.getCollection({ transaction });
+
+ model.set('collectionName', collection.get('name'));
+ model.set('dataSourceKey', collection.get('dataSourceKey'));
+ }
+ });
+
+ this.app.db.on('dataSourcesCollections.afterDestroy', async (model: DataSourcesCollectionModel) => {
+ const dataSource = this.app.dataSourceManager.dataSources.get(model.get('dataSourceKey'));
+ dataSource.collectionManager.removeCollection(model.get('name'));
+ });
+
+ this.app.db.on('dataSourcesFields.afterSaveWithAssociations', async (model: DataSourcesFieldModel) => {
model.load({
app: this.app,
});
@@ -332,11 +369,15 @@ export class PluginDataSourceManagerServer extends Plugin {
});
});
- this.app.db.on('dataSourcesCollections.afterSave', async (model: DataSourcesCollectionModel) => {
- model.load({
- app: this.app,
- });
- });
+ this.app.db.on(
+ 'dataSourcesCollections.afterSaveWithAssociations',
+ async (model: DataSourcesCollectionModel, { transaction }) => {
+ await model.load({
+ app: this.app,
+ transaction,
+ });
+ },
+ );
this.app.db.on('dataSources.afterDestroy', async (model: DataSourceModel) => {
this.app.dataSourceManager.dataSources.delete(model.get('key'));
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts
index 2870f71893..61a9aff450 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts
@@ -67,8 +67,13 @@ export default {
},
});
} else {
- await fieldRecord.update({
- ...values,
+ await mainDb.getRepository('dataSourcesFields').update({
+ filter: {
+ name,
+ collectionName,
+ dataSourceKey,
+ },
+ values,
});
}
@@ -76,6 +81,7 @@ export default {
.get(dataSourceKey)
.collectionManager.getCollection(collectionName)
.getField(name);
+
ctx.body = field.options;
await next();
diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts
index 4abbfde784..095875b335 100644
--- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts
+++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts
@@ -86,11 +86,23 @@ export default {
},
});
} else {
- await dataSourceCollectionRecord.update({
- ...params.values,
+ await ctx.db.getRepository('dataSourcesCollections').update({
+ filter: {
+ name: collectionName,
+ dataSourceKey,
+ },
+ values: params.values,
+ updateAssociationValues: ['fields'],
});
}
+ dataSourceCollectionRecord = await ctx.db.getRepository('dataSourcesCollections').findOne({
+ filter: {
+ name: collectionName,
+ dataSourceKey,
+ },
+ });
+
ctx.body = dataSourceCollectionRecord.toJSON();
await next();
diff --git a/packages/plugins/@nocobase/plugin-gantt/src/client/GanttActionInitializers.tsx b/packages/plugins/@nocobase/plugin-gantt/src/client/GanttActionInitializers.tsx
index 307209f044..594c53a0fc 100644
--- a/packages/plugins/@nocobase/plugin-gantt/src/client/GanttActionInitializers.tsx
+++ b/packages/plugins/@nocobase/plugin-gantt/src/client/GanttActionInitializers.tsx
@@ -8,7 +8,7 @@
*/
import { useFieldSchema } from '@formily/react';
-import { CompatibleSchemaInitializer, useCollection_deprecated } from '@nocobase/client';
+import { CompatibleSchemaInitializer, useActionAvailable, useCollection } from '@nocobase/client';
const commonOptions = {
title: "{{t('Configure actions')}}",
@@ -38,10 +38,7 @@ const commonOptions = {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return !['view', 'file', 'sql'].includes(collection.template) || collection?.writableView;
- },
+ useVisible: () => useActionAvailable('create'),
},
{
type: 'item',
@@ -52,10 +49,7 @@ const commonOptions = {
'x-align': 'right',
'x-decorator': 'ACLActionProvider',
},
- useVisible() {
- const collection = useCollection_deprecated();
- return !['view', 'sql'].includes(collection.template) || collection?.writableView;
- },
+ useVisible: () => useActionAvailable('destroyMany'),
},
{
type: 'item',
@@ -73,9 +67,10 @@ const commonOptions = {
schema: {
'x-align': 'right',
},
+
useVisible() {
const schema = useFieldSchema();
- const collection = useCollection_deprecated();
+ const collection = useCollection();
const { treeTable } = schema?.parent?.['x-decorator-props'] || {};
return collection.tree && treeTable;
},
diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx
index b7d2d31f5a..22e9d40ba3 100644
--- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx
+++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx
@@ -19,8 +19,8 @@ import { SchemaOptionsContext } from '@formily/react';
import {
APIClientProvider,
ApplicationContext,
- CollectionCategroriesContext,
- CollectionCategroriesProvider,
+ CollectionCategoriesContext,
+ CollectionCategoriesProvider,
CurrentAppInfoContext,
DataSourceApplicationProvider,
SchemaComponent,
@@ -390,7 +390,7 @@ export const GraphDrawPage = React.memo(() => {
const {
data: { database },
} = currentAppInfo;
- const categoryCtx = useContext(CollectionCategroriesContext);
+ const categoryCtx = useContext(CollectionCategoriesContext);
const scope = { ...options?.scope };
const components = { ...options?.components };
const saveGraphPositionAction = async (data) => {
@@ -520,7 +520,7 @@ export const GraphDrawPage = React.memo(() => {
-
+
{/* TODO: 因为画布中的卡片是一次性注册进 Graph 的,这里的 theme 是存在闭包里的,因此当主题动态变更时,并不会触发卡片的重新渲染 */}
@@ -531,7 +531,7 @@ export const GraphDrawPage = React.memo(() => {
-
+
diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx
index 100917b445..6597f054d2 100644
--- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx
+++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx
@@ -12,7 +12,7 @@ import { css } from '@emotion/css';
import { SchemaOptionsContext } from '@formily/react';
import { uid } from '@formily/shared';
import {
- CollectionCategroriesContext,
+ CollectionCategoriesContext,
CollectionProvider_deprecated,
SchemaComponent,
SchemaComponentProvider,
@@ -387,7 +387,7 @@ const Entity: React.FC<{
data: { database },
} = useCurrentAppInfo();
const collectionData = useRef();
- const categoryData = useContext(CollectionCategroriesContext);
+ const categoryData = useContext(CollectionCategoriesContext);
collectionData.current = { ...item, title, inherits: item.inherits && new Proxy(item.inherits, {}) };
const { category = [] } = item;
const compile = useCompile();
diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanActionInitializers.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanActionInitializers.tsx
index cec4eedc41..1a86f8475e 100644
--- a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanActionInitializers.tsx
+++ b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanActionInitializers.tsx
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { CompatibleSchemaInitializer, useCollection_deprecated } from '@nocobase/client';
+import { CompatibleSchemaInitializer, useCollection, useActionAvailable } from '@nocobase/client';
const commonOptions = {
title: "{{t('Configure actions')}}",
@@ -35,10 +35,7 @@ const commonOptions = {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return (collection as any).template !== 'view' || collection?.writableView;
- },
+ useVisible: () => useActionAvailable('create'),
},
],
};
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/MapActionInitializers.tsx b/packages/plugins/@nocobase/plugin-map/src/client/block/MapActionInitializers.tsx
index 7c6234e657..7956afbd5e 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/block/MapActionInitializers.tsx
+++ b/packages/plugins/@nocobase/plugin-map/src/client/block/MapActionInitializers.tsx
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { CompatibleSchemaInitializer, useCollection_deprecated } from '@nocobase/client';
+import { CompatibleSchemaInitializer, useActionAvailable } from '@nocobase/client';
const commonOptions = {
title: "{{t('Configure actions')}}",
@@ -35,10 +35,7 @@ const commonOptions = {
skipScopeCheck: true,
},
},
- useVisible() {
- const collection = useCollection_deprecated();
- return collection.template !== 'sql';
- },
+ useVisible: () => useActionAvailable('create'),
},
{
name: 'refresh',
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/fields/circle.ts b/packages/plugins/@nocobase/plugin-map/src/client/fields/circle.ts
index 7852e5511a..92cb14d3a0 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/fields/circle.ts
+++ b/packages/plugins/@nocobase/plugin-map/src/client/fields/circle.ts
@@ -16,7 +16,7 @@ export class CircleFieldInterface extends CommonSchema {
group = 'map';
order = 3;
title = generateNTemplate('Circle');
- availableTypes = ['circle'];
+ availableTypes = ['circle', 'json'];
description = generateNTemplate('Circle');
sortable = true;
default = {
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/fields/lineString.ts b/packages/plugins/@nocobase/plugin-map/src/client/fields/lineString.ts
index b9194292d3..caea2b38eb 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/fields/lineString.ts
+++ b/packages/plugins/@nocobase/plugin-map/src/client/fields/lineString.ts
@@ -17,7 +17,7 @@ export class LineStringFieldInterface extends CommonSchema {
order = 2;
title = generateNTemplate('Line');
description = generateNTemplate('Line');
- availableTypes = ['lineString'];
+ availableTypes = ['lineString', 'json'];
sortable = true;
default = {
type: 'lineString',
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/fields/point.ts b/packages/plugins/@nocobase/plugin-map/src/client/fields/point.ts
index d943ecb244..e61ecde6ff 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/fields/point.ts
+++ b/packages/plugins/@nocobase/plugin-map/src/client/fields/point.ts
@@ -17,7 +17,7 @@ export class PointFieldInterface extends CommonSchema {
order = 1;
title = generateNTemplate('Point');
description = generateNTemplate('Point');
- availableTypes = ['point'];
+ availableTypes = ['point', 'json'];
sortable = true;
default = {
type: 'point',
diff --git a/packages/plugins/@nocobase/plugin-map/src/client/fields/polygon.ts b/packages/plugins/@nocobase/plugin-map/src/client/fields/polygon.ts
index 2365983be5..b24aa23400 100644
--- a/packages/plugins/@nocobase/plugin-map/src/client/fields/polygon.ts
+++ b/packages/plugins/@nocobase/plugin-map/src/client/fields/polygon.ts
@@ -17,7 +17,7 @@ export class PolygonFieldInterface extends CommonSchema {
order = 4;
title = generateNTemplate('Polygon');
description = generateNTemplate('Polygon');
- availableTypes = ['polygon'];
+ availableTypes = ['polygon', 'json'];
sortable = true;
default = {
type: 'polygon',
diff --git a/packages/plugins/@nocobase/plugin-workflow-mailer/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-mailer/src/server/__tests__/instruction.test.ts
new file mode 100644
index 0000000000..e5c8b883e4
--- /dev/null
+++ b/packages/plugins/@nocobase/plugin-workflow-mailer/src/server/__tests__/instruction.test.ts
@@ -0,0 +1,588 @@
+/**
+ * This file is part of the NocoBase (R) project.
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
+ * Authors: NocoBase Team.
+ *
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
+ * For more information, please refer to: https://www.nocobase.com/agreement.
+ */
+
+import { Server } from 'http';
+import jwt from 'jsonwebtoken';
+import Koa from 'koa';
+import bodyParser from 'koa-bodyparser';
+
+import Database from '@nocobase/database';
+import { MockServer } from '@nocobase/test';
+
+import PluginWorkflow, { Processor, EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow';
+import { getApp, sleep } from '@nocobase/plugin-workflow-test';
+
+import { RequestConfig } from '../MailerInstruction';
+
+const HOST = 'localhost';
+
+class MockAPI {
+ app: Koa;
+ server: Server;
+ port: number;
+ get URL_DATA() {
+ return `http://${HOST}:${this.port}/api/data`;
+ }
+ get URL_400() {
+ return `http://${HOST}:${this.port}/api/400`;
+ }
+ get URL_400_MESSAGE() {
+ return `http://${HOST}:${this.port}/api/400_message`;
+ }
+ get URL_400_OBJECT() {
+ return `http://${HOST}:${this.port}/api/400_object`;
+ }
+ get URL_404() {
+ return `http://${HOST}:${this.port}/api/404`;
+ }
+ get URL_TIMEOUT() {
+ return `http://${HOST}:${this.port}/api/timeout`;
+ }
+ get URL_END() {
+ return `http://${HOST}:${this.port}/api/end`;
+ }
+ constructor() {
+ this.app = new Koa();
+ this.app.use(bodyParser());
+
+ this.app.use(async (ctx, next) => {
+ if (ctx.path === '/api/400') {
+ return ctx.throw(400);
+ }
+ if (ctx.path === '/api/400_message') {
+ return ctx.throw(400, 'bad request message');
+ }
+ if (ctx.path === '/api/400_object') {
+ ctx.body = { a: 1 };
+ ctx.status = 400;
+ return;
+ }
+ if (ctx.path === '/api/end') {
+ ctx.res.socket.end();
+ return;
+ }
+ if (ctx.path === '/api/timeout') {
+ await sleep(2000);
+ ctx.status = 204;
+ return;
+ }
+ if (ctx.path === '/api/data') {
+ await sleep(100);
+ ctx.body = {
+ meta: { title: ctx.query.title },
+ data: ctx.request.body,
+ };
+ }
+ await next();
+ });
+ }
+
+ async start() {
+ return new Promise((resolve) => {
+ this.server = this.app.listen(0, () => {
+ this.port = this.server.address()['port'];
+ resolve(true);
+ });
+ });
+ }
+
+ async close() {
+ return new Promise((resolve) => {
+ this.server.close(() => {
+ resolve(true);
+ });
+ });
+ }
+}
+
+describe('workflow > instructions > request', () => {
+ let app: MockServer;
+ let db: Database;
+ let PostRepo;
+ let PostCollection;
+ let ReplyRepo;
+ let WorkflowModel;
+ let workflow;
+ let api: MockAPI;
+
+ beforeEach(async () => {
+ api = new MockAPI();
+ api.start();
+ app = await getApp({
+ resourcer: {
+ prefix: '/api',
+ },
+ plugins: ['users', 'auth', 'workflow-request'],
+ });
+
+ db = app.db;
+ WorkflowModel = db.getCollection('workflows').model;
+ PostCollection = db.getCollection('posts');
+ PostRepo = PostCollection.repository;
+ ReplyRepo = db.getCollection('replies').repository;
+
+ workflow = await WorkflowModel.create({
+ enabled: true,
+ type: 'collection',
+ config: {
+ mode: 1,
+ collection: 'posts',
+ },
+ });
+ });
+
+ afterEach(async () => {
+ await api.close();
+ await app.destroy();
+ });
+
+ describe('request static app routes', () => {
+ it('get data (legacy)', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'GET',
+ onlyData: true,
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result).toMatchObject({ meta: {}, data: {} });
+ });
+
+ it('get data', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result).toMatchObject({
+ data: { meta: {}, data: {} },
+ });
+ });
+
+ it('timeout', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_TIMEOUT,
+ method: 'GET',
+ timeout: 250,
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(1000);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.FAILED);
+
+ expect(job.result).toMatchObject({
+ code: 'ECONNABORTED',
+ name: 'Error',
+ status: null,
+ message: 'timeout of 250ms exceeded',
+ });
+
+ // NOTE: to wait for the response to finish and avoid non finished promise.
+ await sleep(1500);
+ });
+
+ it('ignoreFail', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_TIMEOUT,
+ method: 'GET',
+ timeout: 250,
+ ignoreFail: true,
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(1000);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result).toMatchObject({
+ code: 'ECONNABORTED',
+ name: 'Error',
+ status: null,
+ message: 'timeout of 250ms exceeded',
+ });
+ });
+
+ it('response 400 without body', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_400,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.FAILED);
+ expect(job.result.status).toBe(400);
+ });
+
+ it('response 400 with text message', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_400_MESSAGE,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.FAILED);
+ expect(job.result.status).toBe(400);
+ expect(job.result.data).toBe('bad request message');
+ });
+
+ it('response 400 with object', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_400_OBJECT,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.FAILED);
+ expect(job.result.status).toBe(400);
+ expect(job.result.data).toEqual({ a: 1 });
+ });
+
+ it('response just end', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_END,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.FAILED);
+ expect(job.result).toMatchObject({
+ code: 'ECONNRESET',
+ name: 'Error',
+ status: null,
+ message: 'socket hang up',
+ });
+ });
+
+ it('response 400 ignoreFail', async () => {
+ await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_400,
+ method: 'GET',
+ timeout: 1000,
+ ignoreFail: true,
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.status).toBe(400);
+ });
+
+ it('request with data', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: { title: '{{$context.data.title}}' },
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toEqual({ title: 't1' });
+ });
+
+ // TODO(bug): should not use ejs
+ it('request json data with multiple lines', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: { title: '{{$context.data.title}}' },
+ } as RequestConfig,
+ });
+
+ const title = 't1\n\nline 2';
+ await PostRepo.create({
+ values: { title },
+ });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toEqual({ title });
+ });
+
+ it.skip('request inside loop', async () => {
+ const n1 = await workflow.createNode({
+ type: 'loop',
+ config: {
+ target: 2,
+ },
+ });
+
+ const n2 = await workflow.createNode({
+ type: 'request',
+ upstreamId: n1.id,
+ branchIndex: 0,
+ config: {
+ url: api.URL_DATA,
+ method: 'GET',
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const jobs = await execution.getJobs({ order: [['id', 'ASC']] });
+ expect(jobs.length).toBe(3);
+ expect(jobs.map((item) => item.status)).toEqual(Array(3).fill(JOB_STATUS.RESOLVED));
+ expect(jobs[0].result).toBe(2);
+ });
+ });
+
+ describe('contentType', () => {
+ it('no contentType as "application/json"', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: { a: '{{$context.data.title}}' },
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toEqual({ a: 't1' });
+ });
+
+ it('contentType as "application/x-www-form-urlencoded"', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: [
+ { name: 'a', value: '{{$context.data.title}}' },
+ { name: 'a', value: '&=1' },
+ ],
+ contentType: 'application/x-www-form-urlencoded',
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toEqual({ a: ['t1', '&=1'] });
+ });
+ });
+
+ describe('invalid characters', () => {
+ it('\\n in header value should be trimed, and should not cause error', async () => {
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'POST',
+ data: { a: '{{$context.data.title}}' },
+ headers: [{ name: 'Authorization', value: 'abc\n' }],
+ },
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toEqual({ a: 't1' });
+ });
+ });
+
+ describe('request db resource', () => {
+ it('request db resource', async () => {
+ const user = await db.getRepository('users').create({});
+
+ const token = jwt.sign(
+ {
+ userId: typeof user.id,
+ },
+ process.env.APP_KEY,
+ {
+ expiresIn: '1d',
+ },
+ );
+
+ const server = app.listen(12346, () => {});
+
+ await sleep(1000);
+
+ const n1 = await workflow.createNode({
+ type: 'request',
+ config: {
+ url: `http://localhost:12346/api/categories`,
+ method: 'POST',
+ headers: [{ name: 'Authorization', value: `Bearer ${token}` }],
+ } as RequestConfig,
+ });
+
+ await PostRepo.create({ values: { title: 't1' } });
+
+ await sleep(500);
+
+ const category = await db.getRepository('categories').findOne({});
+
+ const [execution] = await workflow.getExecutions();
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data.data).toMatchObject({});
+
+ server.close();
+ });
+ });
+
+ describe('sync request', () => {
+ let syncFlow;
+
+ beforeEach(async () => {
+ syncFlow = await WorkflowModel.create({
+ type: 'syncTrigger',
+ enabled: true,
+ });
+ });
+
+ it('sync trigger', async () => {
+ await syncFlow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_DATA,
+ method: 'GET',
+ } as RequestConfig,
+ });
+
+ const workflowPlugin = app.pm.get(PluginWorkflow) as PluginWorkflow;
+ const processor = (await workflowPlugin.trigger(syncFlow, { data: { title: 't1' } })) as Processor;
+
+ const [execution] = await syncFlow.getExecutions();
+ expect(processor.execution.id).toEqual(execution.id);
+ expect(processor.execution.status).toBe(execution.status);
+ expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.data).toEqual({ meta: {}, data: {} });
+ });
+
+ it('ignoreFail', async () => {
+ await syncFlow.createNode({
+ type: 'request',
+ config: {
+ url: api.URL_404,
+ method: 'GET',
+ ignoreFail: true,
+ } as RequestConfig,
+ });
+
+ const workflowPlugin = app.pm.get(PluginWorkflow) as PluginWorkflow;
+ const processor = (await workflowPlugin.trigger(syncFlow, { data: { title: 't1' } })) as Processor;
+
+ const [execution] = await syncFlow.getExecutions();
+ const [job] = await execution.getJobs();
+ expect(job.status).toBe(JOB_STATUS.RESOLVED);
+ expect(job.result.status).toBe(404);
+ });
+ });
+});