refactor: rewrite get executions using useExecutions with RQ

This commit is contained in:
Rıdvan Akca
2024-03-12 14:33:09 +03:00
parent f07b6d105a
commit 3bc0c23e5a
7 changed files with 37 additions and 627 deletions

View File

@@ -1,70 +0,0 @@
import { raw } from 'objection';
import { DateTime } from 'luxon';
import Execution from '../../models/execution.js';
import paginate from '../../helpers/pagination.js';
const getExecutions = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const filters = params.filters;
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const selectStatusStatement = `
case
when count(*) filter (where execution_steps.status = 'failure') > 0
then 'failure'
else 'success'
end
as status
`;
const executions = executionBaseQuery
.clone()
.joinRelated('executionSteps as execution_steps')
.select('executions.*', raw(selectStatusStatement))
.groupBy('executions.id')
.orderBy('created_at', 'desc');
const computedExecutions = Execution.query()
.with('executions', executions)
.withSoftDeleted()
.withGraphFetched({
flow: {
steps: true,
},
});
if (filters?.flowId) {
computedExecutions.where('executions.flow_id', filters.flowId);
}
if (filters?.status) {
computedExecutions.where('executions.status', filters.status);
}
if (filters?.createdAt) {
const createdAtFilter = filters.createdAt;
if (createdAtFilter.from) {
const isoFromDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.from, 10)
).toISO();
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
}
if (createdAtFilter.to) {
const isoToDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.to, 10)
).toISO();
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
}
}
return paginate(computedExecutions, params.limit, params.offset);
};
export default getExecutions;

View File

@@ -1,472 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import appConfig from '../../config/app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../test/factories/role';
import { createPermission } from '../../../test/factories/permission';
import { createUser } from '../../../test/factories/user';
import { createFlow } from '../../../test/factories/flow';
import { createStep } from '../../../test/factories/step';
import { createExecution } from '../../../test/factories/execution';
import { createExecutionStep } from '../../../test/factories/execution-step';
describe('graphQL getExecutions query', () => {
const query = `
query {
getExecutions(limit: 10, offset: 0) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
describe('and without correct permissions', () => {
it('should throw not authorized error', async () => {
const userWithoutPermissions = await createUser();
const token = createAuthTokenByUserId(userWithoutPermissions.id);
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
describe('and with correct permission', () => {
let role,
currentUser,
anotherUser,
token,
flowOne,
stepOneForFlowOne,
stepTwoForFlowOne,
executionOne,
flowTwo,
stepOneForFlowTwo,
stepTwoForFlowTwo,
executionTwo,
flowThree,
stepOneForFlowThree,
stepTwoForFlowThree,
executionThree,
expectedResponseForExecutionOne,
expectedResponseForExecutionTwo,
expectedResponseForExecutionThree;
beforeEach(async () => {
role = await createRole({
key: 'sample',
name: 'sample',
});
currentUser = await createUser({
roleId: role.id,
fullName: 'Current User',
});
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
flowOne = await createFlow({
userId: currentUser.id,
});
stepOneForFlowOne = await createStep({
flowId: flowOne.id,
});
stepTwoForFlowOne = await createStep({
flowId: flowOne.id,
});
executionOne = await createExecution({
flowId: flowOne.id,
});
await createExecutionStep({
executionId: executionOne.id,
stepId: stepOneForFlowOne.id,
status: 'success',
});
await createExecutionStep({
executionId: executionOne.id,
stepId: stepTwoForFlowOne.id,
status: 'success',
});
flowTwo = await createFlow({
userId: currentUser.id,
});
stepOneForFlowTwo = await createStep({
flowId: flowTwo.id,
});
stepTwoForFlowTwo = await createStep({
flowId: flowTwo.id,
});
executionTwo = await createExecution({
flowId: flowTwo.id,
});
await createExecutionStep({
executionId: executionTwo.id,
stepId: stepOneForFlowTwo.id,
status: 'success',
});
await createExecutionStep({
executionId: executionTwo.id,
stepId: stepTwoForFlowTwo.id,
status: 'failure',
});
flowThree = await createFlow({
userId: anotherUser.id,
});
stepOneForFlowThree = await createStep({
flowId: flowThree.id,
});
stepTwoForFlowThree = await createStep({
flowId: flowThree.id,
});
executionThree = await createExecution({
flowId: flowThree.id,
});
await createExecutionStep({
executionId: executionThree.id,
stepId: stepOneForFlowThree.id,
status: 'success',
});
await createExecutionStep({
executionId: executionThree.id,
stepId: stepTwoForFlowThree.id,
status: 'failure',
});
expectedResponseForExecutionOne = {
node: {
createdAt: executionOne.createdAt.getTime().toString(),
flow: {
active: flowOne.active,
id: flowOne.id,
name: flowOne.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
},
],
},
id: executionOne.id,
status: 'success',
testRun: executionOne.testRun,
updatedAt: executionOne.updatedAt.getTime().toString(),
},
};
expectedResponseForExecutionTwo = {
node: {
createdAt: executionTwo.createdAt.getTime().toString(),
flow: {
active: flowTwo.active,
id: flowTwo.id,
name: flowTwo.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
},
],
},
id: executionTwo.id,
status: 'failure',
testRun: executionTwo.testRun,
updatedAt: executionTwo.updatedAt.getTime().toString(),
},
};
expectedResponseForExecutionThree = {
node: {
createdAt: executionThree.createdAt.getTime().toString(),
flow: {
active: flowThree.active,
id: flowThree.id,
name: flowThree.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
},
],
},
id: executionThree.id,
status: 'failure',
testRun: executionThree.testRun,
updatedAt: executionThree.updatedAt.getTime().toString(),
},
};
});
describe('and with isCreator condition', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: ['isCreator'],
});
});
it('should return executions data of the current user', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [
expectedResponseForExecutionTwo,
expectedResponseForExecutionOne,
],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and without isCreator condition', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: [],
});
});
it('should return executions data of all users', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [
expectedResponseForExecutionThree,
expectedResponseForExecutionTwo,
expectedResponseForExecutionOne,
],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with filters', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: [],
});
});
it('should return executions data for the specified flow', async () => {
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should return only executions data with success status', async () => {
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should return only executions data within date range', async () => {
const createdAtFrom = executionOne.createdAt.getTime().toString();
const createdAtTo = executionOne.createdAt.getTime().toString();
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -7,7 +7,6 @@ import getConnectedApps from './queries/get-connected-apps.js';
import getCurrentUser from './queries/get-current-user.js';
import getDynamicData from './queries/get-dynamic-data.js';
import getDynamicFields from './queries/get-dynamic-fields.js';
import getExecutions from './queries/get-executions.js';
import getFlow from './queries/get-flow.js';
import getFlows from './queries/get-flows.js';
import getInvoices from './queries/get-invoices.ee.js';
@@ -38,7 +37,6 @@ const queryResolvers = {
getCurrentUser,
getDynamicData,
getDynamicFields,
getExecutions,
getFlow,
getFlows,
getInvoices,

View File

@@ -13,11 +13,6 @@ type Query {
name: String
): FlowConnection
getStepWithTestExecutions(stepId: String!): [Step]
getExecutions(
limit: Int!
offset: Int!
filters: ExecutionFiltersInput
): ExecutionConnection
getDynamicData(
stepId: String!
key: String!
@@ -301,15 +296,6 @@ type Flow {
status: FlowStatus
}
type Execution {
id: String
testRun: Boolean
createdAt: String
updatedAt: String
status: String
flow: Flow
}
type SamlAuthProvider {
id: String
name: String
@@ -609,19 +595,10 @@ type PageInfo {
totalPages: Int!
}
type ExecutionEdge {
node: Execution
}
type ExecutionStepEdge {
node: ExecutionStep
}
type ExecutionConnection {
edges: [ExecutionEdge]
pageInfo: PageInfo
}
type ExecutionStepConnection {
edges: [ExecutionStepEdge]
pageInfo: PageInfo
@@ -781,17 +758,6 @@ type Notification {
description: String
}
input ExecutionCreatedAtFilterInput {
from: String
to: String
}
input ExecutionFiltersInput {
flowId: String
createdAt: ExecutionCreatedAtFilterInput
status: String
}
schema {
query: Query
mutation: Mutation