From e4eb146169761e6ce92b2a6eca005ccf43e5fc0a Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 24 Oct 2023 17:29:43 +0000 Subject: [PATCH 01/16] feat(queries/get-executions): add flowId filter support --- packages/backend/src/graphql/queries/get-executions.ts | 9 +++++++++ packages/backend/src/graphql/schema.graphql | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index dec2ed38..377a033f 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -3,9 +3,14 @@ import Context from '../../types/express/context'; import Execution from '../../models/execution'; import paginate from '../../helpers/pagination'; +type Filters = { + flowId?: string; +} + type Params = { limit: number; offset: number; + filters?: Filters; }; const getExecutions = async ( @@ -41,6 +46,10 @@ const getExecutions = async ( .groupBy('executions.id') .orderBy('updated_at', 'desc'); + if (params.filters?.flowId) { + executions.where('flow_id', params.filters.flowId); + } + return paginate(executions, params.limit, params.offset); }; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 1c98a8b3..97c534d1 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -20,7 +20,11 @@ type Query { ): FlowConnection getStepWithTestExecutions(stepId: String!): [Step] getExecution(executionId: String!): Execution - getExecutions(limit: Int!, offset: Int!): ExecutionConnection + getExecutions( + limit: Int! + offset: Int! + filters: ExecutionFiltersInput + ): ExecutionConnection getExecutionSteps( executionId: String! limit: Int! @@ -795,6 +799,10 @@ type Notification { description: String } +input ExecutionFiltersInput { + flowId: String +} + schema { query: Query mutation: Mutation From 2fa360e40052c09b6e911c118bd72e4eeff46050 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 24 Oct 2023 20:19:52 +0000 Subject: [PATCH 02/16] feat(queries/get-executions): add status filter support --- .../backend/src/graphql/queries/get-executions.ts | 13 ++++++++++--- packages/backend/src/graphql/schema.graphql | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 377a033f..36910371 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -5,6 +5,7 @@ import paginate from '../../helpers/pagination'; type Filters = { flowId?: string; + status?: string; } type Params = { @@ -19,6 +20,7 @@ const getExecutions = async ( context: Context ) => { const conditions = context.currentUser.can('read', 'Execution'); + const filters = params.filters; const userExecutions = context.currentUser.$relatedQuery('executions'); const allExecutions = Execution.query(); @@ -46,11 +48,16 @@ const getExecutions = async ( .groupBy('executions.id') .orderBy('updated_at', 'desc'); - if (params.filters?.flowId) { - executions.where('flow_id', params.filters.flowId); + const computedExecutions = Execution.query().with('executions', executions); + + if (filters?.flowId) { + computedExecutions.where('executions.flow_id', filters.flowId); } - return paginate(executions, params.limit, params.offset); + if (filters?.status) { + computedExecutions.where('executions.status', filters.status); + } + return paginate(computedExecutions, params.limit, params.offset); }; export default getExecutions; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 97c534d1..ddc4f519 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -801,6 +801,7 @@ type Notification { input ExecutionFiltersInput { flowId: String + status: String } schema { From d851db22d09b0d7cabf025e13bfc02eff8d8f84f Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Tue, 24 Oct 2023 20:20:13 +0000 Subject: [PATCH 03/16] feat(queries/get-executions): add updatedAt filter support --- .../src/graphql/queries/get-executions.ts | 17 +++++++++++++++++ packages/backend/src/graphql/schema.graphql | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 36910371..65d85b86 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -6,6 +6,10 @@ import paginate from '../../helpers/pagination'; type Filters = { flowId?: string; status?: string; + updatedAt?: { + from?: string; + to?: string; + }; } type Params = { @@ -20,6 +24,7 @@ const getExecutions = async ( context: Context ) => { const conditions = context.currentUser.can('read', 'Execution'); + const filters = params.filters; const userExecutions = context.currentUser.$relatedQuery('executions'); @@ -57,6 +62,18 @@ const getExecutions = async ( if (filters?.status) { computedExecutions.where('executions.status', filters.status); } + + if (filters?.updatedAt) { + const updatedAtFilter = filters.updatedAt; + if (updatedAtFilter.from) { + computedExecutions.where('executions.updated_at', '>=', updatedAtFilter.from); + } + + if (updatedAtFilter.to) { + computedExecutions.where('executions.updated_at', '<=', updatedAtFilter.to); + } + } + return paginate(computedExecutions, params.limit, params.offset); }; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index ddc4f519..2b54264d 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -799,8 +799,14 @@ type Notification { description: String } +input ExecutionUpdatedAtFilterInput { + from: String + to: String +} + input ExecutionFiltersInput { flowId: String + updatedAt: ExecutionUpdatedAtFilterInput status: String } From 2760526def4091e270aed5de5dda72b0bf560c51 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 25 Oct 2023 10:31:40 +0000 Subject: [PATCH 04/16] feat: add flow_id index in executions --- ...0231025101146_add_flow_id_index_in_executions.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/backend/src/db/migrations/20231025101146_add_flow_id_index_in_executions.ts diff --git a/packages/backend/src/db/migrations/20231025101146_add_flow_id_index_in_executions.ts b/packages/backend/src/db/migrations/20231025101146_add_flow_id_index_in_executions.ts new file mode 100644 index 00000000..ea98e45f --- /dev/null +++ b/packages/backend/src/db/migrations/20231025101146_add_flow_id_index_in_executions.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.table('executions', (table) => { + table.index('flow_id'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.table('executions', (table) => { + table.dropIndex('flow_id'); + }); +} From 14b7053ed84ca0b099aede9bc730d0d250e1fcfe Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 25 Oct 2023 10:31:49 +0000 Subject: [PATCH 05/16] feat: add updated_at index in executions --- ...1025101923_add_updated_at_index_in_executions.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 packages/backend/src/db/migrations/20231025101923_add_updated_at_index_in_executions.ts diff --git a/packages/backend/src/db/migrations/20231025101923_add_updated_at_index_in_executions.ts b/packages/backend/src/db/migrations/20231025101923_add_updated_at_index_in_executions.ts new file mode 100644 index 00000000..6dc51bb6 --- /dev/null +++ b/packages/backend/src/db/migrations/20231025101923_add_updated_at_index_in_executions.ts @@ -0,0 +1,13 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.schema.table('executions', (table) => { + table.index('updated_at'); + }); +} + +export async function down(knex: Knex): Promise { + await knex.schema.table('executions', (table) => { + table.dropIndex('updated_at'); + }); +} From 1b21bbe5b79ee935c04a470ead49e7ae92e21ae6 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 25 Oct 2023 10:32:28 +0000 Subject: [PATCH 06/16] feat(queries/get-executions): accept timestamp instead of ISO datetime --- .../backend/src/graphql/queries/get-executions.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 65d85b86..53b6ca3d 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -1,4 +1,5 @@ import { raw } from 'objection'; +import { DateTime } from 'luxon'; import Context from '../../types/express/context'; import Execution from '../../models/execution'; import paginate from '../../helpers/pagination'; @@ -66,11 +67,21 @@ const getExecutions = async ( if (filters?.updatedAt) { const updatedAtFilter = filters.updatedAt; if (updatedAtFilter.from) { - computedExecutions.where('executions.updated_at', '>=', updatedAtFilter.from); + const isoFromDateTime = DateTime + .fromMillis( + parseInt(updatedAtFilter.from, 10) + ) + .toISO(); + computedExecutions.where('executions.updated_at', '>=', isoFromDateTime); } if (updatedAtFilter.to) { - computedExecutions.where('executions.updated_at', '<=', updatedAtFilter.to); + const isoToDateTime = DateTime + .fromMillis( + parseInt(updatedAtFilter.to, 10) + ) + .toISO(); + computedExecutions.where('executions.updated_at', '<=', isoToDateTime); } } From e185ceb385ee81cf4559eebd0c8a62187d7adaea Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 25 Oct 2023 12:05:53 +0000 Subject: [PATCH 07/16] fix(queries/get-executions): recover flow and steps relations --- .../backend/src/graphql/queries/get-executions.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 53b6ca3d..9ea50144 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -45,16 +45,18 @@ const getExecutions = async ( .clone() .joinRelated('executionSteps as execution_steps') .select('executions.*', raw(selectStatusStatement)) + .groupBy('executions.id') + .orderBy('updated_at', 'desc'); + + const computedExecutions = Execution + .query() + .with('executions', executions) .withSoftDeleted() .withGraphFetched({ flow: { steps: true, }, - }) - .groupBy('executions.id') - .orderBy('updated_at', 'desc'); - - const computedExecutions = Execution.query().with('executions', executions); + }); if (filters?.flowId) { computedExecutions.where('executions.flow_id', filters.flowId); From ffaf9b6e0c516d245ba832b64442f728b8c7b52e Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 26 Oct 2023 17:15:10 +0200 Subject: [PATCH 08/16] chore: Add date types to IFlow and IExecution --- packages/types/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index e8a70979..2e4e97d7 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -51,8 +51,8 @@ export interface IExecution { testRun: boolean; status: 'success' | 'failure'; executionSteps: IExecutionStep[]; - updatedAt: string; - createdAt: string; + updatedAt: string | Date; + createdAt: string | Date; } export interface IStep { @@ -83,8 +83,8 @@ export interface IFlow { active: boolean; status: 'paused' | 'published' | 'draft'; steps: IStep[]; - createdAt: string; - updatedAt: string; + createdAt: string | Date; + updatedAt: string | Date; remoteWebhookId: string; lastInternalId: () => Promise; } From 6567d24760e582b9a35ab54eef39cfd0ae8f2e10 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 26 Oct 2023 17:15:50 +0200 Subject: [PATCH 09/16] test: Adjust app key of step depending on type --- packages/backend/test/factories/step.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend/test/factories/step.ts b/packages/backend/test/factories/step.ts index 409adda2..baaaf090 100644 --- a/packages/backend/test/factories/step.ts +++ b/packages/backend/test/factories/step.ts @@ -13,7 +13,9 @@ export const createStep = async (params: Partial = {}) => { .first(); params.position = params?.position || (lastStep?.position || 0) + 1; - params.status = params?.status || 'incomplete'; + params.status = params?.status || 'completed'; + params.appKey = + params?.appKey || (params.type === 'action' ? 'webhook' : 'deepl'); const [step] = await global.knex.table('steps').insert(params).returning('*'); From 945c52dd6b4487c33af2a75763b47123c6d5b4ab Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 26 Oct 2023 17:16:42 +0200 Subject: [PATCH 10/16] test: Add createdAt and updatedAt defaultst to execution and flow --- packages/backend/test/factories/execution.ts | 2 ++ packages/backend/test/factories/flow.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/backend/test/factories/execution.ts b/packages/backend/test/factories/execution.ts index 65597e9f..34b45fa0 100644 --- a/packages/backend/test/factories/execution.ts +++ b/packages/backend/test/factories/execution.ts @@ -4,6 +4,8 @@ import { createFlow } from './flow'; export const createExecution = async (params: Partial = {}) => { params.flowId = params?.flowId || (await createFlow()).id; params.testRun = params?.testRun || false; + params.createdAt = params?.createdAt || new Date().toISOString(); + params.updatedAt = params?.updatedAt || new Date().toISOString(); const [execution] = await global.knex .table('executions') diff --git a/packages/backend/test/factories/flow.ts b/packages/backend/test/factories/flow.ts index c33581d1..b43a2b62 100644 --- a/packages/backend/test/factories/flow.ts +++ b/packages/backend/test/factories/flow.ts @@ -4,6 +4,8 @@ import { createUser } from './user'; export const createFlow = async (params: Partial = {}) => { params.userId = params?.userId || (await createUser()).id; params.name = params?.name || 'Name your flow!'; + params.createdAt = params?.createdAt || new Date().toISOString(); + params.updatedAt = params?.updatedAt || new Date().toISOString(); const [flow] = await global.knex.table('flows').insert(params).returning('*'); From 9abfaec4d54984011599b8575f34d6f49971480b Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 26 Oct 2023 17:17:12 +0200 Subject: [PATCH 11/16] test: Adjust permission factory to pass all values --- packages/backend/test/factories/permission.ts | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/backend/test/factories/permission.ts b/packages/backend/test/factories/permission.ts index d82d96f6..aae670ea 100644 --- a/packages/backend/test/factories/permission.ts +++ b/packages/backend/test/factories/permission.ts @@ -1,24 +1,15 @@ -import { IPermission } from '@automatisch/types'; +import Permission from '../../src/models/permission'; import { createRole } from './role'; -type PermissionParams = { - roleId?: string; - action?: string; - subject?: string; -}; - -export const createPermission = async ( - params: PermissionParams = {} -): Promise => { - const permissionData = { - roleId: params?.roleId || (await createRole()).id, - action: params?.action || 'read', - subject: params?.subject || 'User', - }; +export const createPermission = async (params: Partial = {}) => { + params.roleId = params?.roleId || (await createRole()).id; + params.action = params?.action || 'read'; + params.subject = params?.subject || 'User'; + params.conditions = params?.conditions || ['isCreator']; const [permission] = await global.knex .table('permissions') - .insert(permissionData) + .insert(params) .returning('*'); return permission; From 96341976f5f3db54a02cfe01f11becd387a43e82 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 26 Oct 2023 17:19:37 +0200 Subject: [PATCH 12/16] test: Add graphQL query test for getExecutions --- .../graphql/queries/get-executions.test.ts | 494 ++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 packages/backend/src/graphql/queries/get-executions.test.ts diff --git a/packages/backend/src/graphql/queries/get-executions.test.ts b/packages/backend/src/graphql/queries/get-executions.test.ts new file mode 100644 index 00000000..5705f1a8 --- /dev/null +++ b/packages/backend/src/graphql/queries/get-executions.test.ts @@ -0,0 +1,494 @@ +import request from 'supertest'; +import app from '../../app'; +import appConfig from '../../config/app'; +import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id'; +import { IJSONObject } from '@automatisch/types'; +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'; +import { IRole, IUser, IExecution, IFlow, IStep } from '@automatisch/types'; + +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 + } + } + } + } + } + } + `; + + const invalidToken = 'invalid-token'; + + describe('with unauthenticated user', () => { + it('should throw not authorized error', async () => { + const response = await request(app) + .post('/graphql') + .set('Authorization', invalidToken) + .send({ query }) + .expect(200); + + expect(response.body.errors).toBeDefined(); + expect(response.body.errors[0].message).toEqual('Not Authorised!'); + }); + }); + + describe('with authenticated user', () => { + describe('and without 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: IRole, + currentUser: IUser, + anotherUser: IUser, + token: string, + flowOne: IFlow, + stepOneForFlowOne: IStep, + stepTwoForFlowOne: IStep, + executionOne: IExecution, + flowTwo: IFlow, + stepOneForFlowTwo: IStep, + stepTwoForFlowTwo: IStep, + executionTwo: IExecution, + flowThree: IFlow, + stepOneForFlowThree: IStep, + stepTwoForFlowThree: IStep, + executionThree: IExecution, + expectedResponseForExecutionOne: IJSONObject, + expectedResponseForExecutionTwo: IJSONObject, + expectedResponseForExecutionThree: IJSONObject; + + 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 as Date).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 as Date).getTime().toString(), + }, + }; + + expectedResponseForExecutionTwo = { + node: { + createdAt: (executionTwo.createdAt as Date).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 as Date).getTime().toString(), + }, + }; + + expectedResponseForExecutionThree = { + node: { + createdAt: (executionThree.createdAt as Date).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 as Date).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 updatedAtFrom = (executionOne.updatedAt as Date) + .getTime() + .toString(); + + const updatedAtTo = (executionOne.updatedAt as Date) + .getTime() + .toString(); + + const query = ` + query { + getExecutions(limit: 10, offset: 0, filters: { updatedAt: { from: "${updatedAtFrom}", to: "${updatedAtTo}" }}) { + 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); + }); + }); + }); + }); +}); From 7245a0a5990cb05ef9847a4afba09a7deb19c537 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 26 Oct 2023 15:29:28 +0000 Subject: [PATCH 13/16] refactor(queries/get-executions): use createdAt in filters --- .../src/graphql/queries/get-executions.ts | 20 +++++++++---------- packages/backend/src/graphql/schema.graphql | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 9ea50144..64b2d6b3 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -7,7 +7,7 @@ import paginate from '../../helpers/pagination'; type Filters = { flowId?: string; status?: string; - updatedAt?: { + createdAt?: { from?: string; to?: string; }; @@ -46,7 +46,7 @@ const getExecutions = async ( .joinRelated('executionSteps as execution_steps') .select('executions.*', raw(selectStatusStatement)) .groupBy('executions.id') - .orderBy('updated_at', 'desc'); + .orderBy('created_at', 'desc'); const computedExecutions = Execution .query() @@ -66,24 +66,24 @@ const getExecutions = async ( computedExecutions.where('executions.status', filters.status); } - if (filters?.updatedAt) { - const updatedAtFilter = filters.updatedAt; - if (updatedAtFilter.from) { + if (filters?.createdAt) { + const createdAtFilter = filters.createdAt; + if (createdAtFilter.from) { const isoFromDateTime = DateTime .fromMillis( - parseInt(updatedAtFilter.from, 10) + parseInt(createdAtFilter.from, 10) ) .toISO(); - computedExecutions.where('executions.updated_at', '>=', isoFromDateTime); + computedExecutions.where('executions.created_at', '>=', isoFromDateTime); } - if (updatedAtFilter.to) { + if (createdAtFilter.to) { const isoToDateTime = DateTime .fromMillis( - parseInt(updatedAtFilter.to, 10) + parseInt(createdAtFilter.to, 10) ) .toISO(); - computedExecutions.where('executions.updated_at', '<=', isoToDateTime); + computedExecutions.where('executions.created_at', '<=', isoToDateTime); } } diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 2b54264d..f5b50500 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -799,14 +799,14 @@ type Notification { description: String } -input ExecutionUpdatedAtFilterInput { +input ExecutionCreatedAtFilterInput { from: String to: String } input ExecutionFiltersInput { flowId: String - updatedAt: ExecutionUpdatedAtFilterInput + createdAt: ExecutionCreatedAtFilterInput status: String } From 2cfa64c2a3466d5e7e0d32bd0e3e7f3d206b18a0 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 26 Oct 2023 15:31:08 +0000 Subject: [PATCH 14/16] test(queries/get-executions): use createdAt in filter test cases --- packages/backend/src/graphql/queries/get-executions.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/graphql/queries/get-executions.test.ts b/packages/backend/src/graphql/queries/get-executions.test.ts index 5705f1a8..32758cbd 100644 --- a/packages/backend/src/graphql/queries/get-executions.test.ts +++ b/packages/backend/src/graphql/queries/get-executions.test.ts @@ -435,17 +435,17 @@ describe('graphQL getExecutions query', () => { }); it('should return only executions data within date range', async () => { - const updatedAtFrom = (executionOne.updatedAt as Date) + const createdAtFrom = (executionOne.createdAt as Date) .getTime() .toString(); - const updatedAtTo = (executionOne.updatedAt as Date) + const createdAtTo = (executionOne.createdAt as Date) .getTime() .toString(); const query = ` query { - getExecutions(limit: 10, offset: 0, filters: { updatedAt: { from: "${updatedAtFrom}", to: "${updatedAtTo}" }}) { + getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) { pageInfo { currentPage totalPages From 3549fef71c0d1ff37ac12fd9d6d48dbf441b7bef Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 26 Oct 2023 15:39:38 +0000 Subject: [PATCH 15/16] feat(ExecutionRow): use createdAt instead of updatedAt --- packages/web/src/components/ExecutionRow/index.tsx | 10 ++++++---- packages/web/src/locales/en.json | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/web/src/components/ExecutionRow/index.tsx b/packages/web/src/components/ExecutionRow/index.tsx index 1baa49a3..31cef9b6 100644 --- a/packages/web/src/components/ExecutionRow/index.tsx +++ b/packages/web/src/components/ExecutionRow/index.tsx @@ -23,8 +23,10 @@ export default function ExecutionRow( const { execution } = props; const { flow } = execution; - const updatedAt = DateTime.fromMillis(parseInt(execution.updatedAt, 10)); - const relativeUpdatedAt = updatedAt.toRelative(); + const createdAt = DateTime.fromMillis( + parseInt(execution.createdAt as string, 10) + ); + const relativeCreatedAt = createdAt.toRelative(); return ( @@ -41,8 +43,8 @@ export default function ExecutionRow( - {formatMessage('execution.updatedAt', { - datetime: relativeUpdatedAt, + {formatMessage('execution.createdAt', { + datetime: relativeCreatedAt, })} diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index 0931fe67..4c082efb 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -89,7 +89,7 @@ "executions.title": "Executions", "executions.noExecutions": "There is no execution data point to show.", "execution.id": "Execution ID: {id}", - "execution.updatedAt": "updated {datetime}", + "execution.createdAt": "created {datetime}", "execution.test": "Test run", "execution.statusSuccess": "Success", "execution.statusFailure": "Failure", From 9f8eb985e4b1b2980c9203e3c6962178c5cc562f Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 26 Oct 2023 15:40:11 +0000 Subject: [PATCH 16/16] refactor: assert entry dates as string --- packages/web/src/components/ExecutionHeader/index.tsx | 8 ++++++-- packages/web/src/components/FlowRow/index.tsx | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/web/src/components/ExecutionHeader/index.tsx b/packages/web/src/components/ExecutionHeader/index.tsx index 15fde3e4..71745ffa 100644 --- a/packages/web/src/components/ExecutionHeader/index.tsx +++ b/packages/web/src/components/ExecutionHeader/index.tsx @@ -39,11 +39,15 @@ function ExecutionId(props: Pick) { } function ExecutionDate(props: Pick) { - const createdAt = DateTime.fromMillis(parseInt(props.createdAt, 10)); + const createdAt = DateTime.fromMillis( + parseInt(props.createdAt as string, 10) + ); const relativeCreatedAt = createdAt.toRelative(); return ( - + {relativeCreatedAt} diff --git a/packages/web/src/components/FlowRow/index.tsx b/packages/web/src/components/FlowRow/index.tsx index 0a6858e3..194f1869 100644 --- a/packages/web/src/components/FlowRow/index.tsx +++ b/packages/web/src/components/FlowRow/index.tsx @@ -65,8 +65,8 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement { setAnchorEl(contextButtonRef.current); }; - const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10)); - const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10)); + const createdAt = DateTime.fromMillis(parseInt(flow.createdAt as string, 10)); + const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt as string, 10)); const isUpdated = updatedAt > createdAt; const relativeCreatedAt = createdAt.toRelative(); const relativeUpdatedAt = updatedAt.toRelative();