mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +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;
|
retryOf?: string;
|
||||||
retrySuccessId?: string;
|
retrySuccessId?: string;
|
||||||
workflowData: IWorkflowBase | WorkflowWithSharingsAndCredentials;
|
workflowData: IWorkflowBase | WorkflowWithSharingsAndCredentials;
|
||||||
|
customData: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatted data to save memory when saving in database or transferring
|
// Flatted data to save memory when saving in database or transferring
|
||||||
@@ -158,6 +159,7 @@ export interface IExecutionFlattedDb extends IExecutionBase {
|
|||||||
id: string;
|
id: string;
|
||||||
data: string;
|
data: string;
|
||||||
workflowData: Omit<IWorkflowBase, 'pinData'>;
|
workflowData: Omit<IWorkflowBase, 'pinData'>;
|
||||||
|
customData: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecutionFlattedResponse extends IExecutionFlatted {
|
export interface IExecutionFlattedResponse extends IExecutionFlatted {
|
||||||
|
|||||||
@@ -31,3 +31,5 @@ properties:
|
|||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
format: date-time
|
format: date-time
|
||||||
|
customData:
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -149,27 +149,29 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
|
|||||||
if (!queryParams.relations) {
|
if (!queryParams.relations) {
|
||||||
queryParams.relations = [];
|
queryParams.relations = [];
|
||||||
}
|
}
|
||||||
(queryParams.relations as string[]).push('executionData');
|
(queryParams.relations as string[]).push('executionData', 'metadata');
|
||||||
}
|
}
|
||||||
|
|
||||||
const executions = await this.find(queryParams);
|
const executions = await this.find(queryParams);
|
||||||
|
|
||||||
if (options?.includeData && options?.unflattenData) {
|
if (options?.includeData && options?.unflattenData) {
|
||||||
return executions.map((execution) => {
|
return executions.map((execution) => {
|
||||||
const { executionData, ...rest } = execution;
|
const { executionData, metadata, ...rest } = execution;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
data: parse(executionData.data) as IRunExecutionData,
|
data: parse(executionData.data) as IRunExecutionData,
|
||||||
workflowData: executionData.workflowData,
|
workflowData: executionData.workflowData,
|
||||||
|
customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])),
|
||||||
} as IExecutionResponse;
|
} as IExecutionResponse;
|
||||||
});
|
});
|
||||||
} else if (options?.includeData) {
|
} else if (options?.includeData) {
|
||||||
return executions.map((execution) => {
|
return executions.map((execution) => {
|
||||||
const { executionData, ...rest } = execution;
|
const { executionData, metadata, ...rest } = execution;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
data: execution.executionData.data,
|
data: execution.executionData.data,
|
||||||
workflowData: execution.executionData.workflowData,
|
workflowData: execution.executionData.workflowData,
|
||||||
|
customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])),
|
||||||
} as IExecutionFlattedDb;
|
} as IExecutionFlattedDb;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -219,7 +221,7 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (options?.includeData) {
|
if (options?.includeData) {
|
||||||
findOptions.relations = ['executionData'];
|
findOptions.relations = ['executionData', 'metadata'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const execution = await this.findOne(findOptions);
|
const execution = await this.findOne(findOptions);
|
||||||
@@ -228,19 +230,21 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { executionData, ...rest } = execution;
|
const { executionData, metadata, ...rest } = execution;
|
||||||
|
|
||||||
if (options?.includeData && options?.unflattenData) {
|
if (options?.includeData && options?.unflattenData) {
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
data: parse(execution.executionData.data) as IRunExecutionData,
|
data: parse(execution.executionData.data) as IRunExecutionData,
|
||||||
workflowData: execution.executionData.workflowData,
|
workflowData: execution.executionData.workflowData,
|
||||||
|
customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])),
|
||||||
} as IExecutionResponse;
|
} as IExecutionResponse;
|
||||||
} else if (options?.includeData) {
|
} else if (options?.includeData) {
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
data: execution.executionData.data,
|
data: execution.executionData.data,
|
||||||
workflowData: execution.executionData.workflowData,
|
workflowData: execution.executionData.workflowData,
|
||||||
|
customData: Object.fromEntries(metadata.map((m) => [m.key, m.value])),
|
||||||
} as IExecutionFlattedDb;
|
} 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.
|
// 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
|
// 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)
|
// 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) {
|
if (Object.keys(executionInformation).length > 0) {
|
||||||
await this.update({ id: executionId }, executionInformation);
|
await this.update({ id: executionId }, executionInformation);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '../shared/db/workflows';
|
} from '../shared/db/workflows';
|
||||||
import {
|
import {
|
||||||
createErrorExecution,
|
createErrorExecution,
|
||||||
|
createExecution,
|
||||||
createManyExecutions,
|
createManyExecutions,
|
||||||
createSuccessfulExecution,
|
createSuccessfulExecution,
|
||||||
createWaitingExecution,
|
createWaitingExecution,
|
||||||
@@ -125,6 +126,49 @@ describe('GET /executions/:id', () => {
|
|||||||
expect(response.statusCode).toBe(200);
|
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 () => {
|
test('member should not get an execution of another user without the workflow being shared', async () => {
|
||||||
const workflow = await createWorkflow({}, owner);
|
const workflow = await createWorkflow({}, owner);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user