feat(plugin-workflow-aggregate): add precision option (#6491)

This commit is contained in:
Junyi 2025-03-18 12:20:33 +08:00 committed by GitHub
parent 30c612cb08
commit 7da1c200ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 79 additions and 42 deletions

View File

@ -22,7 +22,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository {
async aggregate(options: AggregateOptions) { async aggregate(options: AggregateOptions) {
const targetRepository = this.targetCollection.repository; const targetRepository = this.targetCollection.repository;
const sourceModel = await this.getSourceModel(); const sourceModel = await this.getSourceModel(await this.getTransaction(options));
const association = this.association as any; const association = this.association as any;

View File

@ -387,6 +387,21 @@ export default class extends Instruction {
}, },
}, },
}, },
precision: {
type: 'number',
title: `{{t("Result precision", { ns: "${NAMESPACE}" })}}`,
description: `{{t("Number of decimal places for query result.", { ns: "${NAMESPACE}" })}}`,
'x-decorator': 'FormItem',
'x-component': 'InputNumber',
'x-component-props': {
min: 0,
max: 14,
step: 1,
precision: 0,
className: 'auto-width',
},
default: 2,
},
}; };
scope = { scope = {
useCollectionDataSource, useCollectionDataSource,

View File

@ -8,5 +8,7 @@
"Data of associated collection": "关联数据表数据", "Data of associated collection": "关联数据表数据",
"Field to aggregate": "聚合字段", "Field to aggregate": "聚合字段",
"Distinct": "去重", "Distinct": "去重",
"Query result": "查询结果" "Query result": "查询结果",
"Result precision": "结果精度",
"Number of decimal places for query result.": "查询结果小数位数"
} }

View File

@ -23,7 +23,7 @@ const aggregators = {
export default class extends Instruction { export default class extends Instruction {
async run(node: FlowNodeModel, input, processor: Processor) { async run(node: FlowNodeModel, input, processor: Processor) {
const { aggregator, associated, collection, association = {}, params = {} } = node.config; const { aggregator, associated, collection, association = {}, params = {}, precision = 2 } = node.config;
const options = processor.getParsedValue(params, node.id); const options = processor.getParsedValue(params, node.id);
const [dataSourceName, collectionName] = parseCollectionName(collection); const [dataSourceName, collectionName] = parseCollectionName(collection);
const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName); const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName);
@ -49,7 +49,10 @@ export default class extends Instruction {
}); });
return { return {
result: round((options.dataType === DataTypes.DOUBLE ? Number(result) : result) || 0, 14), result: round(
(options.dataType === DataTypes.DOUBLE ? Number(result) : result) || 0,
Math.max(0, Math.min(precision, 14)),
),
status: JOB_STATUS.RESOLVED, status: JOB_STATUS.RESOLVED,
}; };
} }

View File

@ -36,6 +36,7 @@ describe('workflow > instructions > aggregate', () => {
TagRepo = db.getCollection('tags').repository; TagRepo = db.getCollection('tags').repository;
workflow = await WorkflowModel.create({ workflow = await WorkflowModel.create({
sync: true,
enabled: true, enabled: true,
type: 'collection', type: 'collection',
config: { config: {
@ -62,8 +63,6 @@ describe('workflow > instructions > aggregate', () => {
const post = await PostRepo.create({ values: { title: 't1' } }); const post = await PostRepo.create({ values: { title: 't1' } });
await sleep(500);
const [execution] = await workflow.getExecutions(); const [execution] = await workflow.getExecutions();
const [job] = await execution.getJobs(); const [job] = await execution.getJobs();
expect(job.result).toBe(1); expect(job.result).toBe(1);
@ -86,8 +85,6 @@ describe('workflow > instructions > aggregate', () => {
const post = await PostRepo.create({ values: { title: 't1' } }); const post = await PostRepo.create({ values: { title: 't1' } });
await sleep(500);
const [execution] = await workflow.getExecutions(); const [execution] = await workflow.getExecutions();
const [job] = await execution.getJobs(); const [job] = await execution.getJobs();
expect(job.result).toBe(0); expect(job.result).toBe(0);
@ -107,16 +104,12 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(1); expect(j1.result).toBe(1);
const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(3); expect(j2.result).toBe(3);
@ -136,22 +129,18 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', score: 0.1 } }); const p1 = await PostRepo.create({ values: { title: 't1', score: 0.1 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(0.1); expect(j1.result).toBe(0.1);
const p2 = await PostRepo.create({ values: { title: 't2', score: 0.2 } }); const p2 = await PostRepo.create({ values: { title: 't2', score: 0.2 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(0.3); expect(j2.result).toBe(0.3);
}); });
it('sum number will be rounded', async () => { it('sum number will be rounded to 2 decimal places by default', async () => {
const n1 = await workflow.createNode({ const n1 = await workflow.createNode({
type: 'aggregate', type: 'aggregate',
config: { config: {
@ -163,9 +152,59 @@ describe('workflow > instructions > aggregate', () => {
}, },
}); });
const p1 = await PostRepo.create({ values: { title: 't1', score: 0.100000000000001 } }); const p1 = await PostRepo.create({ values: { title: 't1', score: 0.123 } });
await sleep(500); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs();
expect(j1.result).toBe(0.12);
const p2 = await PostRepo.create({ values: { title: 't2', score: 0.456 } });
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs();
expect(j2.result).toBe(0.58);
});
it('sum precision configured -1 as 0', async () => {
const n1 = await workflow.createNode({
type: 'aggregate',
config: {
aggregator: 'sum',
collection: 'posts',
params: {
field: 'score',
},
precision: -1,
},
});
const p1 = await PostRepo.create({ values: { title: 't1', score: 0.123 } });
const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs();
expect(j1.result).toBe(0);
const p2 = await PostRepo.create({ values: { title: 't2', score: 0.456 } });
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs();
expect(j2.result).toBe(1);
});
it('sum precision configured over 14 as 14', async () => {
const n1 = await workflow.createNode({
type: 'aggregate',
config: {
aggregator: 'sum',
collection: 'posts',
params: {
field: 'score',
},
precision: 15,
},
});
const p1 = await PostRepo.create({ values: { title: 't1', score: 0.100000000000001 } });
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
@ -173,8 +212,6 @@ describe('workflow > instructions > aggregate', () => {
const p2 = await PostRepo.create({ values: { title: 't2', score: 0.200000000000001 } }); const p2 = await PostRepo.create({ values: { title: 't2', score: 0.200000000000001 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(0.3); expect(j2.result).toBe(0.3);
@ -194,8 +231,6 @@ describe('workflow > instructions > aggregate', () => {
const p2 = await PostRepo.create({ values: { title: 't2' } }); const p2 = await PostRepo.create({ values: { title: 't2' } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(0); expect(j1.result).toBe(0);
@ -215,16 +250,12 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(1); expect(j1.result).toBe(1);
const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(1.5); expect(j2.result).toBe(1.5);
@ -244,16 +275,12 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(1); expect(j1.result).toBe(1);
const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(1); expect(j2.result).toBe(1);
@ -273,16 +300,12 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1] = await e1.getJobs(); const [j1] = await e1.getJobs();
expect(j1.result).toBe(1); expect(j1.result).toBe(1);
const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } });
await sleep(500);
const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] });
const [j2] = await e2.getJobs(); const [j2] = await e2.getJobs();
expect(j2.result).toBe(2); expect(j2.result).toBe(2);
@ -333,8 +356,6 @@ describe('workflow > instructions > aggregate', () => {
const p1 = await PostRepo.create({ values: { title: 't1', comments: [{}, { status: 1 }] } }); const p1 = await PostRepo.create({ values: { title: 't1', comments: [{}, { status: 1 }] } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1, j2] = await e1.getJobs({ order: [['id', 'ASC']] }); const [j1, j2] = await e1.getJobs({ order: [['id', 'ASC']] });
expect(j1.result).toBe(2); expect(j1.result).toBe(2);
@ -343,7 +364,7 @@ describe('workflow > instructions > aggregate', () => {
it('sum', async () => { it('sum', async () => {
const PostModel = db.getCollection('posts').model; const PostModel = db.getCollection('posts').model;
const p1 = await PostModel.create({ title: 't1', read: 1 }); const p1 = await PostModel.create({ title: 't1', read: 1 }, { hooks: false });
const n1 = await workflow.createNode({ const n1 = await workflow.createNode({
type: 'create', type: 'create',
@ -400,8 +421,6 @@ describe('workflow > instructions > aggregate', () => {
const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } });
await sleep(500);
const [e1] = await workflow.getExecutions(); const [e1] = await workflow.getExecutions();
const [j1, j2, j3] = await e1.getJobs({ order: [['id', 'ASC']] }); const [j1, j2, j3] = await e1.getJobs({ order: [['id', 'ASC']] });
expect(j2.result).toBe(3); expect(j2.result).toBe(3);
@ -429,8 +448,6 @@ describe('workflow > instructions > aggregate', () => {
await PostRepo.create({ values: { title: 't1' } }); await PostRepo.create({ values: { title: 't1' } });
await sleep(500);
const [execution] = await workflow.getExecutions(); const [execution] = await workflow.getExecutions();
expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
const [job] = await execution.getJobs(); const [job] = await execution.getJobs();