mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat: Add custom data to public API execution endpoints (#9705)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -31,3 +31,5 @@ properties:
|
||||
type: string
|
||||
nullable: true
|
||||
format: date-time
|
||||
customData:
|
||||
type: object
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user