Merge pull request #1697 from automatisch/rest-get-executions

feat: Implement get executions API endpoint
This commit is contained in:
Ömer Faruk Aydın
2024-03-06 15:08:35 +01:00
committed by GitHub
7 changed files with 208 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const executionsQuery = request.currentUser.authorizedExecutions
.withSoftDeleted()
.orderBy('created_at', 'desc')
.withGraphFetched({
flow: {
steps: true,
},
});
const executions = await paginateRest(executionsQuery, request.query.page);
for (const execution of executions.records) {
const executionSteps = await execution.$relatedQuery('executionSteps');
const status = executionSteps.some((step) => step.status === 'failure')
? 'failure'
: 'success';
execution.status = status;
}
renderObject(response, executions);
};

View File

@@ -0,0 +1,113 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionsMock from '../../../../../test/mocks/rest/api/v1/executions/get-executions';
describe('GET /api/v1/executions', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the executions of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecutionOne = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionTwo = await createExecution({
flowId: currentUserFlow.id,
deletedAt: new Date().toISOString(),
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[currentUserExecutionTwo, currentUserExecutionOne],
currentUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the executions of another user', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecutionOne = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionTwo = await createExecution({
flowId: anotherUserFlow.id,
deletedAt: new Date().toISOString(),
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[anotherUserExecutionTwo, anotherUserExecutionOne],
anotherUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -15,6 +15,10 @@ const authorizationList = {
action: 'read',
subject: 'Execution',
},
'GET /api/v1/executions/': {
action: 'read',
subject: 'Execution',
},
};
export const authorizeUser = async (request, response, next) => {

View File

@@ -2,10 +2,18 @@ import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import getExecutionsAction from '../../../controllers/api/v1/executions/get-executions.js';
import getExecutionAction from '../../../controllers/api/v1/executions/get-execution.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeUser,
asyncHandler(getExecutionsAction)
);
router.get(
'/:executionId',
authenticateUser,

View File

@@ -8,6 +8,10 @@ const executionSerializer = (execution) => {
updatedAt: execution.updatedAt.getTime(),
};
if (execution.status) {
executionData.status = execution.status;
}
if (execution.flow) {
executionData.flow = flowSerializer(execution.flow);
}

View File

@@ -26,6 +26,20 @@ describe('executionSerializer', () => {
expect(executionSerializer(execution)).toEqual(expectedPayload);
});
it('should return the execution data with status', async () => {
execution.status = 'success';
const expectedPayload = {
id: execution.id,
testRun: execution.testRun,
createdAt: execution.createdAt.getTime(),
updatedAt: execution.updatedAt.getTime(),
status: 'success',
};
expect(executionSerializer(execution)).toEqual(expectedPayload);
});
it('should return the execution data with the flow', async () => {
execution.flow = flow;

View File

@@ -0,0 +1,39 @@
const getExecutionsMock = async (executions, flow, steps) => {
const data = executions.map((execution) => ({
id: execution.id,
testRun: execution.testRun,
createdAt: execution.createdAt.getTime(),
updatedAt: execution.updatedAt.getTime(),
status: 'success',
flow: {
id: flow.id,
name: flow.name,
active: flow.active,
status: flow.active ? 'published' : 'draft',
steps: steps.map((step) => ({
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
})),
},
}));
return {
data: data,
meta: {
count: executions.length,
currentPage: 1,
isArray: true,
totalPages: 1,
type: 'Execution',
},
};
};
export default getExecutionsMock;