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:
Junyi 2023-08-15 10:32:51 +07:00 committed by GitHub
parent 3baebf2a71
commit dea4c6f9c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 237 additions and 35 deletions

View File

@ -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);

View File

@ -0,0 +1,2 @@
export const DEFAULT_PAGE = 1;
export const DEFAULT_PER_PAGE = 20;

View File

@ -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 {

View File

@ -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;

View File

@ -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}} 个筛选项",

View File

@ -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 || [];

View File

@ -1,4 +1 @@
export const DEFAULT_PAGE = 1;
export const DEFAULT_PER_PAGE = 20;
export const CACHE_KEY = 'localization:texts';

View File

@ -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,
},

View File

@ -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();

View File

@ -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}" })}}`,

View File

@ -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;

View File

@ -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', () => {

View File

@ -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,
});