feat: Add custom data to public API execution endpoints (#9705)

This commit is contained in:
Val
2024-06-17 12:38:10 +01:00
committed by GitHub
parent d615711728
commit a1046607bf
4 changed files with 59 additions and 6 deletions

View File

@@ -145,6 +145,7 @@ export interface IExecutionResponse extends IExecutionBase {
retryOf?: string;
retrySuccessId?: string;
workflowData: IWorkflowBase | WorkflowWithSharingsAndCredentials;
customData: Record<string, string>;
}
// 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<IWorkflowBase, 'pinData'>;
customData: Record<string, string>;
}
export interface IExecutionFlattedResponse extends IExecutionFlatted {

View File

@@ -31,3 +31,5 @@ properties:
type: string
nullable: true
format: date-time
customData:
type: object

View File

@@ -149,27 +149,29 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
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;
});
}
@@ -219,7 +221,7 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
},
};
if (options?.includeData) {
findOptions.relations = ['executionData'];
findOptions.relations = ['executionData', 'metadata'];
}
const execution = await this.findOne(findOptions);
@@ -228,19 +230,21 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
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;
}
@@ -298,7 +302,8 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
// 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);
}

View File

@@ -12,6 +12,7 @@ import {
} from '../shared/db/workflows';
import {
createErrorExecution,
createExecution,
createManyExecutions,
createSuccessfulExecution,
createWaitingExecution,
@@ -125,6 +126,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);