diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts index f01a678d65794..106e82522fd4d 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts @@ -309,12 +309,14 @@ export class QueryRecordNode { nodeTitle: Locator; nodeConfigure: Locator; collectionDropDown: Locator; - allowMultipleDataBoxesForResults: Locator; + // allowMultipleDataBoxesForResults: Locator; addSortFieldsButton: Locator; pageNumberEditBox: Locator; pageNumberVariableButton: Locator; pageSizeEditBox: Locator; exitProcessOptionsBoxWithEmptyResult: Locator; + singleRecordRadioButton: Locator; + multipleRecordsRadioButton: Locator; submitButton: Locator; cancelButton: Locator; addNodeButton: Locator; @@ -328,7 +330,9 @@ export class QueryRecordNode { this.collectionDropDown = page .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') .locator('.ant-select-selection-search-input'); - this.allowMultipleDataBoxesForResults = page.getByLabel('Allow multiple records as'); + // this.allowMultipleDataBoxesForResults = page.getByLabel('Allow multiple records as'); + this.singleRecordRadioButton = page.getByLabel('block-item-RadioWithTooltip-').getByLabel('Single record'); + this.multipleRecordsRadioButton = page.getByLabel('block-item-RadioWithTooltip-').getByLabel('Multiple records'); this.addSortFieldsButton = page.getByRole('button', { name: 'plus Add sort field' }); this.pageNumberEditBox = page.getByLabel('variable-constant'); this.pageNumberVariableButton = page.getByLabel('variable-button'); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts index 09a8b792cca97..596f320736e0d 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts @@ -150,7 +150,7 @@ test('Collection event add data trigger, no filtering and no sorting, query comm await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); await queryRecordNode.submitButton.click(); @@ -229,7 +229,7 @@ test('Collection event add data trigger, no filter ID ascending, query common ta await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); // 设置排序条件 await queryRecordNode.addSortFieldsButton.click(); await page.getByTestId('select-single').click(); @@ -338,7 +338,7 @@ test('Collection event add data trigger, no filter ID descending, query common t await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); // 设置排序条件 await queryRecordNode.addSortFieldsButton.click(); await page.getByTestId('select-single').click(); @@ -448,7 +448,7 @@ test('Collection event add data trigger, no filtering and no sorting, query mult await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); const pageNumber = faker.number.int({ min: 1, max: 5 }); @@ -552,7 +552,7 @@ test('Collection event add data trigger, no filtering and no sorting, query the await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - // await queryRecordNode.allowMultipleDataBoxesForResults.check(); + // await queryRecordNode.multipleRecordsRadioButton.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); const pageNumber = faker.number.int({ min: 5, max: 5 }); @@ -658,7 +658,7 @@ test('Collection event add data trigger, no filtering and no sorting, query the await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); const pageNumber = faker.number.int({ min: 5, max: 5 }); @@ -787,7 +787,7 @@ test('Collection event add data trigger, filter to meet all conditions (status_s await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: queryNodeCollectionDisplayName }).click(); - // await queryRecordNode.allowMultipleDataBoxesForResults.check(); + // await queryRecordNode.multipleRecordsRadioButton.check(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page.getByLabel('block-item-Filter-workflows-Filter').getByRole('button', { name: 'Select field' }).click(); @@ -903,7 +903,7 @@ test('Collection event add data trigger, filter to satisfy any condition (status await queryRecordNode.collectionDropDown.click(); await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); await page.getByRole('menuitemcheckbox', { name: queryNodeCollectionDisplayName }).click(); - await queryRecordNode.allowMultipleDataBoxesForResults.check(); + await queryRecordNode.multipleRecordsRadioButton.check(); // 设置过滤条件 await page.getByTestId('filter-select-all-or-any').click(); await page.getByRole('option', { name: 'Any' }).click(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/query.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/query.tsx index 957bf1906f6d6..1e5b67afb3624 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/query.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/query.tsx @@ -24,6 +24,7 @@ import { NAMESPACE } from '../locale'; import { appends, collection, filter, pagination, sort } from '../schemas/collection'; import { WorkflowVariableInput, getCollectionFieldOptions } from '../variable'; import { Instruction } from '.'; +import { RadioWithTooltip } from '../components'; export default class extends Instruction { title = `{{t("Query record", { ns: "${NAMESPACE}" })}}`; @@ -49,10 +50,25 @@ export default class extends Instruction { }, multiple: { type: 'boolean', + title: `{{t("Result type", { 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}" })}}`, + 'x-component': 'RadioWithTooltip', + 'x-component-props': { + options: [ + { + label: `{{t("Single record", { ns: "${NAMESPACE}" })}}`, + value: false, + tooltip: `{{t("The result will be an object of the first matching record only, or null if no matched record.", { ns: "${NAMESPACE}" })}}`, + }, + { + label: `{{t("Multiple records", { ns: "${NAMESPACE}" })}}`, + value: true, + tooltip: `{{t("The result will be an array containing matched records, or an empty one if no matching records. This can be used to be processed in a loop node.", { ns: "${NAMESPACE}" })}}`, + }, + ], + }, + required: true, + default: false, }, params: { type: 'object', @@ -112,6 +128,7 @@ export default class extends Instruction { FilterDynamicComponent, SchemaComponentContext, WorkflowVariableInput, + RadioWithTooltip, }; useVariables({ key: name, title, config }, options) { // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json index a39e6722b37d9..591b21fbc0296 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json @@ -173,6 +173,11 @@ "Allow multiple records as result": "允许结果是多条数据", "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.": "选中后,当查询结果有多条记录时,会返回数组作为结果,可以使用循环节点对它逐条操作;否则,仅返回一条数据。", + "Result type": "结果类型", + "Single record": "单条数据", + "Multiple records": "多条数据", + "The result will be an object of the first matching record only, or null if no matched record.": "结果是一个对象,仅为首条匹配的记录,或空值。", + "The result will be an array containing matched records, or an empty one if no matching records. This can be used to be processed in a loop node.": "结果会是一个数组,包含匹配条件的记录,没有匹配记录则为空数组。可以通过循环节点逐个处理。", "Exit when query result is null": "查询结果为空时,退出流程", "Please select collection first": "请先选择数据表", "Only update records matching conditions": "只更新满足条件的数据", diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/instructions/query.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/instructions/query.test.ts index a22b3362dd97c..5460604e958c5 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/instructions/query.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/instructions/query.test.ts @@ -229,30 +229,28 @@ describe('workflow > instructions > query', () => { expect(job.result.posts[0].id).toBe(post.id); }); - it('params.sort', async () => { + it('params.sort & params.page & params.pageSize', async () => { const n1 = await workflow.createNode({ type: 'query', config: { collection: 'posts', params: { sort: [{ field: 'id', direction: 'asc' }], + page: 2, + pageSize: 2, }, }, }); - const p1 = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const p2 = await PostRepo.create({ values: { title: 't2' } }); + await PostCollection.model.bulkCreate([{ title: 't4' }, { title: 't3' }, { title: 't2' }]); + await PostRepo.create({ values: { title: 't1' } }); await sleep(500); - // get the 2nd execution - const [execution] = await workflow.getExecutions({ order: [['id', 'DESC']] }); - expect(execution.context.data.title).toBe(p2.title); + const [execution] = await workflow.getExecutions(); + expect(execution.context.data.title).toBe('t1'); const [job] = await execution.getJobs(); - expect(job.result.title).toBe(p1.title); + expect(job.result.title).toBe('t2'); }); }); @@ -330,22 +328,23 @@ describe('workflow > instructions > query', () => { collection: 'posts', multiple: true, params: { - sort: [{ field: 'id', direction: 'asc' }], - page: 1, - pageSize: 1, + sort: [{ field: 'id', direction: 'desc' }], + page: 2, + pageSize: 2, }, }, }); - const p1 = await PostRepo.create({ values: { title: 't1' } }); - const p2 = await PostRepo.create({ values: { title: 't2' } }); + await PostCollection.model.bulkCreate([{ title: 't1' }, { title: 't2' }, { title: 't3' }]); + await PostRepo.create({ values: { title: 't4' } }); 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); + const e1s = await workflow.getExecutions(); + expect(e1s.length).toBe(1); + const [job] = await e1s[0].getJobs(); + expect(job.result.length).toBe(2); + expect(job.result[0].title).toBe('t2'); }); });