feat(plugin-workflow): add sort and pagination to query node params (#2453)
* refactor(core): expose some utils and constants * feat(plugin-workfow): add sort and pagination to query node params * fix(plugin-workflow): fix job result json style * fix(plugin-workflow): fix sort param * fix(plugin-workflow): fix test cases
This commit is contained in:
parent
3baebf2a71
commit
dea4c6f9c7
@ -1,22 +1,7 @@
|
||||
import { assign } from '@nocobase/utils';
|
||||
import { Context } from '..';
|
||||
import { getRepositoryFromParams } from '../utils';
|
||||
|
||||
export const DEFAULT_PAGE = 1;
|
||||
export const DEFAULT_PER_PAGE = 20;
|
||||
|
||||
function pageArgsToLimitArgs(
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): {
|
||||
offset: number;
|
||||
limit: number;
|
||||
} {
|
||||
return {
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
};
|
||||
}
|
||||
import { getRepositoryFromParams, pageArgsToLimitArgs } from '../utils';
|
||||
import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../constants';
|
||||
|
||||
function totalPage(total, pageSize): number {
|
||||
return Math.ceil(total / pageSize);
|
||||
|
2
packages/core/actions/src/constants.ts
Normal file
2
packages/core/actions/src/constants.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const DEFAULT_PAGE = 1;
|
||||
export const DEFAULT_PER_PAGE = 20;
|
@ -7,6 +7,8 @@ import * as actions from './actions';
|
||||
|
||||
export * as utils from './utils';
|
||||
|
||||
export * from './constants';
|
||||
|
||||
export type Next = () => Promise<any>;
|
||||
|
||||
export interface Context extends Koa.Context {
|
||||
|
@ -1,6 +1,19 @@
|
||||
import { MultipleRelationRepository, Repository } from '@nocobase/database';
|
||||
import { Context } from '.';
|
||||
|
||||
export function pageArgsToLimitArgs(
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): {
|
||||
offset: number;
|
||||
limit: number;
|
||||
} {
|
||||
return {
|
||||
offset: (page - 1) * pageSize,
|
||||
limit: pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
export function getRepositoryFromParams(ctx: Context) {
|
||||
const { resourceName, resourceOf } = ctx.action;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
export default {
|
||||
"Display <1><0>10</0><1>20</1><2>50</2><3>100</3></1> items per page": "每页显示 <1><0>10</0><1>20</1><2>50</2><3>100</3></1> 条",
|
||||
"Page number": "页码",
|
||||
"Page size": "每页条数",
|
||||
"Meet <1><0>All</0><1>Any</1></1> conditions in the group": "满足组内 <1><0>全部</0><1>任意</1></1> 条件",
|
||||
"Open in<1><0>Modal</0><1>Drawer</1><2>Window</2></1>": "在 <1><0>对话框</0><1>抽屉</1><2>窗口</2></1> 内打开",
|
||||
"{{count}} filter items": "{{count}} 个筛选项",
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Context, Next } from '@nocobase/actions';
|
||||
import { Context, Next, DEFAULT_PAGE, DEFAULT_PER_PAGE } from '@nocobase/actions';
|
||||
import { Database, Model, Op } from '@nocobase/database';
|
||||
import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../constans';
|
||||
|
||||
const appendTranslations = async (db: Database, rows: Model[], locale: string): Promise<any[]> => {
|
||||
const texts = rows || [];
|
||||
|
@ -1,4 +1 @@
|
||||
export const DEFAULT_PAGE = 1;
|
||||
export const DEFAULT_PER_PAGE = 20;
|
||||
|
||||
export const CACHE_KEY = 'localization:texts';
|
||||
|
@ -95,10 +95,7 @@ function JobModal() {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.JSON',
|
||||
'x-component-props': {
|
||||
className: css`
|
||||
padding: 1em;
|
||||
background-color: #eee;
|
||||
`,
|
||||
className: styles.nodeJobResultClass,
|
||||
},
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { SchemaInitializerItemOptions, useCollectionDataSource, useCollectionManager, useCompile } from '@nocobase/client';
|
||||
import { ArrayItems } from '@formily/antd-v5';
|
||||
|
||||
import { appends, collection, filter } from '../schemas/collection';
|
||||
import {
|
||||
SchemaComponentContext,
|
||||
SchemaInitializerItemOptions,
|
||||
useCollectionDataSource,
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
} from '@nocobase/client';
|
||||
|
||||
import { appends, collection, filter, pagination, sort } from '../schemas/collection';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { CollectionBlockInitializer } from '../components/CollectionBlockInitializer';
|
||||
import { FilterDynamicComponent } from '../components/FilterDynamicComponent';
|
||||
import { getCollectionFieldOptions } from '../variable';
|
||||
import { FieldsSelect } from '../components/FieldsSelect';
|
||||
import { getCollectionFieldOptions, useWorkflowVariableOptions } from '../variable';
|
||||
import { useForm } from '@formily/react';
|
||||
|
||||
export default {
|
||||
title: `{{t("Query record", { ns: "${NAMESPACE}" })}}`,
|
||||
@ -16,31 +24,70 @@ export default {
|
||||
collection,
|
||||
multiple: {
|
||||
type: 'boolean',
|
||||
title: `{{t("Allow multiple records as result", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': `{{t("Allow multiple records as result", { ns: "${NAMESPACE}" })}}`,
|
||||
description: `{{t("If checked, when there are multiple records in the query result, an array will be returned as the result, which can be operated on one by one using a loop node. Otherwise, only one record will be returned.", { ns: "${NAMESPACE}" })}}`,
|
||||
},
|
||||
params: {
|
||||
type: 'object',
|
||||
'x-component': 'fieldset',
|
||||
properties: {
|
||||
filter,
|
||||
sort,
|
||||
pagination,
|
||||
appends,
|
||||
},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['collection'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{$deps[0] != null}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
failOnEmpty: {
|
||||
type: 'boolean',
|
||||
title: `{{t("Exit when query result is null", { ns: "${NAMESPACE}" })}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': `{{t("Exit when query result is null", { ns: "${NAMESPACE}" })}}`,
|
||||
},
|
||||
},
|
||||
view: {},
|
||||
scope: {
|
||||
useCollectionDataSource,
|
||||
useWorkflowVariableOptions,
|
||||
useSortableFields() {
|
||||
const compile = useCompile();
|
||||
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||
const { values } = useForm();
|
||||
const fields = getCollectionFields(values.collection);
|
||||
return fields
|
||||
.filter((field: any) => {
|
||||
if (!field.interface) {
|
||||
return false;
|
||||
}
|
||||
const fieldInterface = getInterface(field.interface);
|
||||
if (fieldInterface?.sortable) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((field: any) => {
|
||||
return {
|
||||
value: field.name,
|
||||
label: field?.uiSchema?.title ? compile(field?.uiSchema?.title) : field.name,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
components: {
|
||||
ArrayItems,
|
||||
FilterDynamicComponent,
|
||||
SchemaComponentContext,
|
||||
},
|
||||
useVariables({ id, title, config }, options) {
|
||||
const compile = useCompile();
|
||||
|
@ -49,6 +49,123 @@ export const filter = {
|
||||
},
|
||||
};
|
||||
|
||||
export const sort = {
|
||||
type: 'array',
|
||||
title: '{{t("Sort")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
space: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
field: {
|
||||
type: 'string',
|
||||
enum: '{{useSortableFields()}}',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: 260,
|
||||
},
|
||||
},
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-component-props': {
|
||||
optionType: 'button',
|
||||
},
|
||||
enum: [
|
||||
{
|
||||
label: '{{t("ASC")}}',
|
||||
value: 'asc',
|
||||
},
|
||||
{
|
||||
label: '{{t("DESC")}}',
|
||||
value: 'desc',
|
||||
},
|
||||
],
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{t("Add sort field")}}',
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const pagination = {
|
||||
type: 'void',
|
||||
title: '{{t("Pagination")}}',
|
||||
'x-decorator': 'SchemaComponentContext.Provider',
|
||||
'x-decorator-props': {
|
||||
value: { designable: false },
|
||||
},
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
page: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
page: {
|
||||
type: 'number',
|
||||
title: '{{t("Page number")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope: '{{useWorkflowVariableOptions()}}',
|
||||
useTypedConstant: ['number', 'null'],
|
||||
},
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
pageSize: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
title: '{{t("Page size")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
min: 1,
|
||||
max: 100,
|
||||
},
|
||||
default: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const appends = {
|
||||
type: 'array',
|
||||
title: `{{t("Preload associations", { ns: "${NAMESPACE}" })}}`,
|
||||
|
@ -320,6 +320,11 @@ const useStyles = createStyles(({ css, token }) => {
|
||||
align-items: center;
|
||||
`,
|
||||
|
||||
nodeJobResultClass: css`
|
||||
padding: 1em;
|
||||
background-color: ${token.colorBgContainer};
|
||||
`,
|
||||
|
||||
addButtonClass: css`
|
||||
flex-shrink: 0;
|
||||
padding: 2em 0;
|
||||
|
@ -226,7 +226,7 @@ describe('workflow > instructions > query', () => {
|
||||
config: {
|
||||
collection: 'posts',
|
||||
params: {
|
||||
sort: 'id',
|
||||
sort: [{ field: 'id', direction: 'asc' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -313,6 +313,31 @@ describe('workflow > instructions > query', () => {
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result.length).toBe(0);
|
||||
});
|
||||
|
||||
it('params.sort & params.page & params.pageSize', async () => {
|
||||
const n1 = await workflow.createNode({
|
||||
type: 'query',
|
||||
config: {
|
||||
collection: 'posts',
|
||||
multiple: true,
|
||||
params: {
|
||||
sort: [{ field: 'id', direction: 'asc' }],
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||
const p2 = await PostRepo.create({ values: { title: 't2' } });
|
||||
|
||||
await sleep(500);
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result.length).toBe(1);
|
||||
expect(job.result[0].title).toBe(p1.title);
|
||||
});
|
||||
});
|
||||
|
||||
describe('failOnEmpty', () => {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { DEFAULT_PAGE, DEFAULT_PER_PAGE, utils } from '@nocobase/actions';
|
||||
|
||||
import Processor from '../Processor';
|
||||
import { JOB_STATUS } from '../constants';
|
||||
import { toJSON } from '../utils';
|
||||
@ -8,7 +10,12 @@ export default {
|
||||
const { collection, multiple, params = {}, failOnEmpty = false } = node.config;
|
||||
|
||||
const repo = (<typeof FlowNodeModel>node.constructor).database.getRepository(collection);
|
||||
const options = processor.getParsedValue(params, node);
|
||||
const {
|
||||
page = DEFAULT_PAGE,
|
||||
pageSize = DEFAULT_PER_PAGE,
|
||||
sort = [],
|
||||
...options
|
||||
} = processor.getParsedValue(params, node);
|
||||
const appends = options.appends
|
||||
? Array.from(
|
||||
options.appends.reduce((set, field) => {
|
||||
@ -20,7 +27,11 @@ export default {
|
||||
: options.appends;
|
||||
const result = await (multiple ? repo.find : repo.findOne).call(repo, {
|
||||
...options,
|
||||
appends: appends,
|
||||
...utils.pageArgsToLimitArgs(page, pageSize),
|
||||
sort: sort
|
||||
.filter((item) => item.field)
|
||||
.map((item) => `${item.direction?.toLowerCase() === 'desc' ? '-' : ''}${item.field}`),
|
||||
appends,
|
||||
transaction: processor.transaction,
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user