Merge branch 'main' into next
Some checks are pending
Build Docker Image / build-and-push (push) Waiting to run
Build Pro Image / build-and-push (push) Waiting to run
E2E / Build (push) Waiting to run
E2E / Core and plugins (push) Blocked by required conditions
E2E / plugin-workflow (push) Blocked by required conditions
E2E / plugin-workflow-approval (push) Blocked by required conditions
E2E / plugin-data-source-main (push) Blocked by required conditions
E2E / Comment on PR (push) Blocked by required conditions
NocoBase Backend Test / sqlite-test (20, false) (push) Waiting to run
NocoBase Backend Test / sqlite-test (20, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (public, 20, public, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, nocobase, true) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, false) (push) Waiting to run
NocoBase Backend Test / postgres-test (user_schema, 20, public, true) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, false) (push) Waiting to run
NocoBase Backend Test / mysql-test (20, true) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, false) (push) Waiting to run
NocoBase Backend Test / mariadb-test (20, true) (push) Waiting to run
NocoBase FrontEnd Test / frontend-test (18) (push) Waiting to run
Test on Windows / build (push) Waiting to run

This commit is contained in:
GitHub Actions Bot 2024-09-06 07:08:05 +00:00
commit 840dd3714a
6 changed files with 108 additions and 29 deletions

View File

@ -51,6 +51,12 @@ export default class extends Instruction {
`, `,
}, },
}, },
withMeta: {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': `{{t("Include meta information of this query in result", { ns: "${NAMESPACE}" })}}`,
},
}; };
scope = { scope = {
sqlDescription() { sqlDescription() {

View File

@ -119,7 +119,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -215,7 +215,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordLength = sqlNodeJobResult[0].length; const nodeResultRecordLength = sqlNodeJobResult.length;
expect(nodeResultRecordLength).toBe(8); expect(nodeResultRecordLength).toBe(8);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -317,7 +317,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -419,7 +419,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordLength = sqlNodeJobResult[0].length; const nodeResultRecordLength = sqlNodeJobResult.length;
expect(nodeResultRecordLength).toBe(7); expect(nodeResultRecordLength).toBe(7);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -524,7 +524,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe(triggerNodeCollectionRecordOne); expect(nodeResultRecordOrgname).toBe(triggerNodeCollectionRecordOne);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -629,7 +629,7 @@ test.describe('select data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordLength = sqlNodeJobResult[0].length; const nodeResultRecordLength = sqlNodeJobResult.length;
expect(nodeResultRecordLength).toBe(8); expect(nodeResultRecordLength).toBe(8);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -713,10 +713,10 @@ test.describe('insert data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
const insertRecordId = sqlNodeJobResult[0][0].id; const insertRecordId = sqlNodeJobResult[0].id;
const getRecords = await apiGetRecord(SQLNodeCollectionName, insertRecordId); const getRecords = await apiGetRecord(SQLNodeCollectionName, insertRecordId);
const getRecordsObj = JSON.parse(JSON.stringify(getRecords)); const getRecordsObj = JSON.parse(JSON.stringify(getRecords));
expect(getRecordsObj.orgname).toBe('公司名称(单行文本)1'); expect(getRecordsObj.orgname).toBe('公司名称(单行文本)1');
@ -801,7 +801,7 @@ test.describe('insert data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordLength = sqlNodeJobResult[0].length; const nodeResultRecordLength = sqlNodeJobResult.length;
expect(nodeResultRecordLength).toBe(3); expect(nodeResultRecordLength).toBe(3);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -903,7 +903,7 @@ test.describe('insert data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
// 4、后置处理删除工作流 // 4、后置处理删除工作流
await apiDeleteWorkflow(workflowId); await apiDeleteWorkflow(workflowId);
@ -1001,7 +1001,7 @@ test.describe('update data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordLength = sqlNodeJobResult[0].length; const nodeResultRecordLength = sqlNodeJobResult.length;
expect(nodeResultRecordLength).toBe(8); expect(nodeResultRecordLength).toBe(8);
// 4、后置处理删除工作流 // 4、后置处理删除工作流
@ -1107,7 +1107,7 @@ test.describe('update data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('orgname'); expect(nodeResultRecordOrgname).toBe('orgname');
// 4、后置处理删除工作流 // 4、后置处理删除工作流
@ -1206,7 +1206,7 @@ test.describe('delete data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
// 4、后置处理删除工作流 // 4、后置处理删除工作流
@ -1312,7 +1312,7 @@ test.describe('delete data', () => {
const jobs = getWorkflowNodeExecutionsObj[0].jobs; const jobs = getWorkflowNodeExecutionsObj[0].jobs;
const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId); const sqlNodeJob = jobs.find((job) => job.nodeId.toString() === sqlNodeId);
const sqlNodeJobResult = sqlNodeJob.result; const sqlNodeJobResult = sqlNodeJob.result;
const nodeResultRecordOrgname = sqlNodeJobResult[0][0].orgname; const nodeResultRecordOrgname = sqlNodeJobResult[0].orgname;
expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1'); expect(nodeResultRecordOrgname).toBe('公司名称(单行文本)1');
// 4、后置处理删除工作流 // 4、后置处理删除工作流

View File

@ -2,5 +2,6 @@
"SQL action": "SQL 操作", "SQL action": "SQL 操作",
"Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句", "Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句",
"Select a data source to execute SQL.": "选择一个数据源来执行 SQL", "Select a data source to execute SQL.": "选择一个数据源来执行 SQL",
"SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用(商业插件)。" "SQL query result could be used through <1>JSON query node</1> (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点</1> 中使用(商业插件)。",
"Include meta information of this query in result": "在结果中包含此查询的元信息"
} }

View File

@ -11,10 +11,9 @@ import { Processor, Instruction, JOB_STATUS, FlowNodeModel } from '@nocobase/plu
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 dataSourceName = node.config.dataSource || 'main';
// @ts-ignore // @ts-ignore
const { db } = this.workflow.app.dataSourceManager.dataSources.get( const { db } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName).collectionManager;
node.config.dataSource || 'main',
).collectionManager;
if (!db) { if (!db) {
throw new Error(`type of data source "${node.config.dataSource}" is not database`); throw new Error(`type of data source "${node.config.dataSource}" is not database`);
} }
@ -26,14 +25,14 @@ export default class extends Instruction {
} }
// @ts-ignore // @ts-ignore
const result = await db.sequelize.query(sql, { const [result, meta] = await db.sequelize.query(sql, {
transaction: processor.transaction, transaction: this.workflow.useDataSourceTransaction(dataSourceName, processor.transaction),
// plain: true, // plain: true,
// model: db.getCollection(node.config.collection).model // model: db.getCollection(node.config.collection).model
}); });
return { return {
result, result: node.config.withMeta ? [result, meta] : result,
status: JOB_STATUS.RESOLVED, status: JOB_STATUS.RESOLVED,
}; };
} }

View File

@ -117,8 +117,6 @@ describe('workflow > instructions > sql', () => {
const [execution] = await workflow.getExecutions(); const [execution] = await workflow.getExecutions();
const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] });
expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
// expect(queryJob.status).toBe(JOB_STATUS.RESOLVED);
// expect(queryJob.result.read).toBe(post.id);
}); });
it('update', async () => { it('update', async () => {
@ -208,20 +206,56 @@ describe('workflow > instructions > sql', () => {
}, },
}); });
const CategoryCollection = db.getCollection('categories');
const n1 = await w2.createNode({ const n1 = await w2.createNode({
type: 'sql', type: 'sql',
config: { config: {
sql: `select count(id) from ${PostCollection.quotedTableName()}`, sql: `select count(id) as count from ${CategoryCollection.quotedTableName()}`,
}, },
}); });
const CategoryRepo = db.getCollection('categories').repository; const category = await CategoryCollection.repository.create({ values: { title: 't1' } });
const category = await CategoryRepo.create({ values: { title: 't1' } });
const [execution] = await w2.getExecutions(); const [execution] = await w2.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();
expect(job.status).toBe(JOB_STATUS.RESOLVED); expect(job.status).toBe(JOB_STATUS.RESOLVED);
expect(job.result.length).toBe(1);
expect(job.result[0].count).toBe(1);
});
});
describe('legacy', () => {
it('withMeta', async () => {
const w2 = await WorkflowModel.create({
enabled: true,
sync: true,
type: 'collection',
config: {
mode: 1,
collection: 'categories',
},
});
const CategoryCollection = db.getCollection('categories');
const n1 = await w2.createNode({
type: 'sql',
config: {
sql: `select count(id) as count from ${CategoryCollection.quotedTableName()}`,
withMeta: true,
},
});
const category = await CategoryCollection.repository.create({ values: { title: 't1' } });
const [execution] = await w2.getExecutions();
const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] });
expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED);
expect(sqlJob.result.length).toBe(2);
expect(sqlJob.result[0].length).toBe(1);
expect(sqlJob.result[0][0].count).toBe(1);
}); });
}); });
@ -249,10 +283,9 @@ describe('workflow > instructions > sql', () => {
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();
expect(job.result.length).toBe(2); expect(job.result.length).toBe(1);
expect(job.result[0].length).toBe(1);
// @ts-ignore // @ts-ignore
expect(job.result[0][0].id).toBe(post.id); expect(job.result[0].id).toBe(post.id);
}); });
}); });
}); });

View File

@ -0,0 +1,40 @@
/**
* 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 { Migration } from '@nocobase/server';
export default class extends Migration {
appVersion = '<1.3.16-beta';
async up() {
const { db } = this.context;
const NodeRepo = db.getRepository('flow_nodes');
await db.sequelize.transaction(async (transaction) => {
const nodes = await NodeRepo.find({
filter: {
type: 'sql',
},
transaction,
});
await nodes.reduce(
(promise, node) =>
promise.then(() => {
node.set('config', { ...node.config, withMeta: true });
node.changed('config', true);
return node.save({
silent: true,
transaction,
});
}),
Promise.resolve(),
);
});
}
}