From 4323455adbf0326adb1bbb064dfefe42bc1d04d8 Mon Sep 17 00:00:00 2001 From: Valya Bullions Date: Tue, 11 Jun 2024 14:06:23 +0100 Subject: [PATCH] feat: Add custom data to public API execution endpoints --- packages/cli/src/Interfaces.ts | 2 + .../executions/spec/schemas/execution.yml | 2 + .../repositories/execution.repository.ts | 17 ++++--- .../integration/publicApi/executions.test.ts | 44 +++++++++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index ce46bc994b6a0..85b88662b6194 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -145,6 +145,7 @@ export interface IExecutionResponse extends IExecutionBase { retryOf?: string; retrySuccessId?: string; workflowData: IWorkflowBase | WorkflowWithSharingsAndCredentials; + customData: Record; } // Flatted data to save memory when saving in database or transferring @@ -158,6 +159,7 @@ export interface IExecutionFlattedDb extends IExecutionBase { id: string; data: string; workflowData: Omit; + customData: Record; } export interface IExecutionFlattedResponse extends IExecutionFlatted { diff --git a/packages/cli/src/PublicApi/v1/handlers/executions/spec/schemas/execution.yml b/packages/cli/src/PublicApi/v1/handlers/executions/spec/schemas/execution.yml index baa106fd71fa8..ce40c69ceede0 100644 --- a/packages/cli/src/PublicApi/v1/handlers/executions/spec/schemas/execution.yml +++ b/packages/cli/src/PublicApi/v1/handlers/executions/spec/schemas/execution.yml @@ -31,3 +31,5 @@ properties: type: string nullable: true format: date-time + customData: + type: object diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 33ec7ed7ec993..bf8727fe53223 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -150,27 +150,29 @@ export class ExecutionRepository extends Repository { if (!queryParams.relations) { queryParams.relations = []; } - (queryParams.relations as string[]).push('executionData'); + (queryParams.relations as string[]).push('executionData', 'metadata'); } const executions = await this.find(queryParams); if (options?.includeData && options?.unflattenData) { return executions.map((execution) => { - const { executionData, ...rest } = execution; + const { executionData, metadata, ...rest } = execution; return { ...rest, data: parse(executionData.data) as IRunExecutionData, workflowData: executionData.workflowData, + customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])), } as IExecutionResponse; }); } else if (options?.includeData) { return executions.map((execution) => { - const { executionData, ...rest } = execution; + const { executionData, metadata, ...rest } = execution; return { ...rest, data: execution.executionData.data, workflowData: execution.executionData.workflowData, + customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])), } as IExecutionFlattedDb; }); } @@ -220,7 +222,7 @@ export class ExecutionRepository extends Repository { }, }; if (options?.includeData) { - findOptions.relations = ['executionData']; + findOptions.relations = ['executionData', 'metadata']; } const execution = await this.findOne(findOptions); @@ -229,19 +231,21 @@ export class ExecutionRepository extends Repository { return undefined; } - const { executionData, ...rest } = execution; + const { executionData, metadata, ...rest } = execution; if (options?.includeData && options?.unflattenData) { return { ...rest, data: parse(execution.executionData.data) as IRunExecutionData, workflowData: execution.executionData.workflowData, + customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])), } as IExecutionResponse; } else if (options?.includeData) { return { ...rest, data: execution.executionData.data, workflowData: execution.executionData.workflowData, + customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])), } as IExecutionFlattedDb; } @@ -289,7 +293,8 @@ export class ExecutionRepository extends Repository { // Se isolate startedAt because it must be set when the execution starts and should never change. // So we prevent updating it, if it's sent (it usually is and causes problems to executions that // are resumed after waiting for some time, as a new startedAt is set) - const { id, data, workflowId, workflowData, startedAt, ...executionInformation } = execution; + const { id, data, workflowId, workflowData, startedAt, customData, ...executionInformation } = + execution; if (Object.keys(executionInformation).length > 0) { await this.update({ id: executionId }, executionInformation); } diff --git a/packages/cli/test/integration/publicApi/executions.test.ts b/packages/cli/test/integration/publicApi/executions.test.ts index ce26a19f8fa8f..684dd5faca340 100644 --- a/packages/cli/test/integration/publicApi/executions.test.ts +++ b/packages/cli/test/integration/publicApi/executions.test.ts @@ -12,6 +12,7 @@ import { } from '../shared/db/workflows'; import { createErrorExecution, + createExecution, createManyExecutions, createSuccessfulExecution, createWaitingExecution, @@ -121,6 +122,49 @@ describe('GET /executions/:id', () => { expect(response.statusCode).toBe(200); }); + test('member should not be able to fetch custom data when includeData is not set', async () => { + const workflow = await createWorkflow({}, user1); + const execution = await createExecution( + { + finished: true, + status: 'success', + metadata: [ + { key: 'test1', value: 'value1' }, + { key: 'test2', value: 'value2' }, + ], + }, + workflow, + ); + + const response = await authUser1Agent.get(`/executions/${execution.id}`); + + expect(response.statusCode).toBe(200); + expect(response.body.customData).toBeUndefined(); + }); + + test('member should be able to fetch custom data when includeData=true', async () => { + const workflow = await createWorkflow({}, user1); + const execution = await createExecution( + { + finished: true, + status: 'success', + metadata: [ + { key: 'test1', value: 'value1' }, + { key: 'test2', value: 'value2' }, + ], + }, + workflow, + ); + + const response = await authUser1Agent.get(`/executions/${execution.id}?includeData=true`); + + expect(response.statusCode).toBe(200); + expect(response.body.customData).toEqual({ + test1: 'value1', + test2: 'value2', + }); + }); + test('member should not get an execution of another user without the workflow being shared', async () => { const workflow = await createWorkflow({}, owner);