From 479fa69a08cdfef6864ff231c533ecfc3426eae1 Mon Sep 17 00:00:00 2001 From: kgajowy Date: Thu, 30 Sep 2021 13:09:25 +0200 Subject: [PATCH] feat(async-jobs): add event date --- ...32999358910-AddTimestampToJobStatusView.ts | 61 +++++++++++++++++++ ...9473857-AddTimestampToProjectJobsStatus.ts | 50 +++++++++++++++ .../projects/dto/project-jobs-status.dto.ts | 3 + .../projects/job-status/job-status.service.ts | 3 + .../job-status/job-status.view.api.entity.ts | 37 +++++------ .../project-status.view.api.entity.ts | 6 +- ...job-status.service.integration.e2e-spec.ts | 3 + .../project-job-status.e2e-spec.ts | 4 +- 8 files changed, 147 insertions(+), 20 deletions(-) create mode 100644 api/apps/api/src/migrations/api/1632999358910-AddTimestampToJobStatusView.ts create mode 100644 api/apps/api/src/migrations/api/1632999473857-AddTimestampToProjectJobsStatus.ts diff --git a/api/apps/api/src/migrations/api/1632999358910-AddTimestampToJobStatusView.ts b/api/apps/api/src/migrations/api/1632999358910-AddTimestampToJobStatusView.ts new file mode 100644 index 0000000000..3f0b4ca526 --- /dev/null +++ b/api/apps/api/src/migrations/api/1632999358910-AddTimestampToJobStatusView.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTimestampToJobStatusView1632999358910 + implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP VIEW "scenario_job_status"; + `); + await queryRunner.query(` + CREATE VIEW "scenario_job_status" AS + SELECT + DISTINCT ON (job_type, topic) job_type, + api_events.topic AS scenario_id, + projects.id AS project_id, + api_events.kind, + api_events.data, + api_events.timestamp + FROM + api_events + INNER JOIN scenarios ON api_events.topic = scenarios.id + INNER JOIN projects ON projects.id = scenarios.project_id + CROSS JOIN LATERAL SUBSTRING( + api_events.kind + FROM + 'scenario.#"[^.]*#"%' FOR '#' + ) AS job_type + ORDER BY + job_type, + api_events.topic, + api_events.timestamp DESC; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP VIEW "scenario_job_status"; + `); + await queryRunner.query(` + CREATE VIEW "scenario_job_status" AS + SELECT + DISTINCT ON (job_type, topic) job_type, + api_events.topic AS scenario_id, + projects.id AS project_id, + api_events.kind, + api_events.data + FROM + api_events + INNER JOIN scenarios ON api_events.topic = scenarios.id + INNER JOIN projects ON projects.id = scenarios.project_id + CROSS JOIN LATERAL SUBSTRING( + api_events.kind + FROM + 'scenario.#"[^.]*#"%' FOR '#' + ) AS job_type + ORDER BY + job_type, + api_events.topic, + api_events.timestamp DESC; + `); + } +} diff --git a/api/apps/api/src/migrations/api/1632999473857-AddTimestampToProjectJobsStatus.ts b/api/apps/api/src/migrations/api/1632999473857-AddTimestampToProjectJobsStatus.ts new file mode 100644 index 0000000000..184336c14e --- /dev/null +++ b/api/apps/api/src/migrations/api/1632999473857-AddTimestampToProjectJobsStatus.ts @@ -0,0 +1,50 @@ +import { QueryRunner } from 'typeorm'; + +export class AddTimestampToProjectJobsStatus1632999473857 { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP VIEW "project_job_status"; + `); + + await queryRunner.query(` + CREATE VIEW "project_job_status" as + SELECT DISTINCT ON (job_type, topic) job_type, + api_events.topic AS project_id, + api_events.kind, + api_events.data, + api_events.timestamp + FROM api_events + CROSS JOIN LATERAL SUBSTRING( + api_events.kind + FROM + 'project.#"[^.]*#"%' FOR '#' + ) AS job_type + ORDER BY job_type, + api_events.topic, + api_events.timestamp DESC; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP VIEW "project_job_status"; + `); + + await queryRunner.query(` + CREATE VIEW "project_job_status" as + SELECT DISTINCT ON (job_type, topic) job_type, + api_events.topic AS project_id, + api_events.kind, + api_events.data + FROM api_events + CROSS JOIN LATERAL SUBSTRING( + api_events.kind + FROM + 'project.#"[^.]*#"%' FOR '#' + ) AS job_type + ORDER BY job_type, + api_events.topic, + api_events.timestamp DESC; + `); + } +} diff --git a/api/apps/api/src/modules/projects/dto/project-jobs-status.dto.ts b/api/apps/api/src/modules/projects/dto/project-jobs-status.dto.ts index 1254909f8f..8ff3576095 100644 --- a/api/apps/api/src/modules/projects/dto/project-jobs-status.dto.ts +++ b/api/apps/api/src/modules/projects/dto/project-jobs-status.dto.ts @@ -29,6 +29,9 @@ export class ScenarioJobStatus { oneOf: [{ $ref: getSchemaPath(ProgressJobDTO) }], }) data?: ProgressJobDTO; + + @ApiPropertyOptional() + isoDate?: string; } export class ScenarioStatus { diff --git a/api/apps/api/src/modules/projects/job-status/job-status.service.ts b/api/apps/api/src/modules/projects/job-status/job-status.service.ts index afcbc39d57..fccdf31bc9 100644 --- a/api/apps/api/src/modules/projects/job-status/job-status.service.ts +++ b/api/apps/api/src/modules/projects/job-status/job-status.service.ts @@ -13,6 +13,7 @@ export { Status }; export interface Job { kind: JobType; status: Status; + isoDate?: string; } export type ProgressJob = Job & { @@ -68,6 +69,7 @@ export class JobStatusService { kind: status.jobType, status: jobStatus, data: status.publicEventData, + isoDate: new Date(status.timestamp).toISOString(), }); } @@ -79,6 +81,7 @@ export class JobStatusService { status: job.jobStatus!, // guard, or even types, do not play well // with getters data: job.data, + isoDate: new Date(job.timestamp).toISOString(), })), }, }; diff --git a/api/apps/api/src/modules/projects/job-status/job-status.view.api.entity.ts b/api/apps/api/src/modules/projects/job-status/job-status.view.api.entity.ts index 249738ee73..92f6562a4e 100644 --- a/api/apps/api/src/modules/projects/job-status/job-status.view.api.entity.ts +++ b/api/apps/api/src/modules/projects/job-status/job-status.view.api.entity.ts @@ -12,25 +12,23 @@ import { ValuesType } from 'utility-types'; @ViewEntity({ expression: ` - SELECT - DISTINCT ON (job_type, topic) job_type, - api_events.topic AS scenario_id, - projects.id AS project_id, - api_events.kind, - api_events.data - FROM - api_events - INNER JOIN scenarios ON api_events.topic = scenarios.id - INNER JOIN projects ON projects.id = scenarios.project_id - CROSS JOIN LATERAL SUBSTRING( - api_events.kind - FROM - 'scenario.#"[^.]*#"%' FOR '#' + SELECT DISTINCT ON (job_type, topic) job_type, + api_events.topic AS scenario_id, + projects.id AS project_id, + api_events.kind, + api_events.data, + api_events.timestamp + FROM api_events + INNER JOIN scenarios ON api_events.topic = scenarios.id + INNER JOIN projects ON projects.id = scenarios.project_id + CROSS JOIN LATERAL SUBSTRING( + api_events.kind + FROM + 'scenario.#"[^.]*#"%' FOR '#' ) AS job_type - ORDER BY - job_type, - api_events.topic, - api_events.timestamp DESC; + ORDER BY job_type, + api_events.topic, + api_events.timestamp DESC; `, }) export class ScenarioJobStatus { @@ -60,6 +58,9 @@ export class ScenarioJobStatus { @ViewColumn() data!: ApiEvent['data']; + @ViewColumn() + timestamp!: Date; + get publicEventData(): { fractionalProgress: number } | undefined { const data: KnownEventsData | undefined = this.data; if ( diff --git a/api/apps/api/src/modules/projects/job-status/project-status.view.api.entity.ts b/api/apps/api/src/modules/projects/job-status/project-status.view.api.entity.ts index 1cde5d7064..81f6806531 100644 --- a/api/apps/api/src/modules/projects/job-status/project-status.view.api.entity.ts +++ b/api/apps/api/src/modules/projects/job-status/project-status.view.api.entity.ts @@ -10,7 +10,8 @@ import { ValuesType } from 'utility-types'; SELECT DISTINCT ON (job_type, topic) job_type, api_events.topic AS project_id, api_events.kind, - api_events.data + api_events.data, + api_events.timestamp FROM api_events CROSS JOIN LATERAL SUBSTRING( api_events.kind @@ -42,6 +43,9 @@ export class ProjectJobStatus { @ViewColumn() data!: ApiEvent['data']; + + @ViewColumn() + timestamp!: Date; } const eventToJobStatusMapping: Record, JobStatus> = { diff --git a/api/apps/api/test/project-jobs-status/job-status.service.integration.e2e-spec.ts b/api/apps/api/test/project-jobs-status/job-status.service.integration.e2e-spec.ts index 31c80e9b53..ccfdb25d85 100644 --- a/api/apps/api/test/project-jobs-status/job-status.service.integration.e2e-spec.ts +++ b/api/apps/api/test/project-jobs-status/job-status.service.integration.e2e-spec.ts @@ -47,10 +47,12 @@ describe(`when has two projects with scenarios and events`, () => { { kind: 'costSurface', status: 'done', + isoDate: expect.any(String), }, { kind: 'planningUnitsInclusion', status: 'failure', + isoDate: expect.any(String), }, ], scenarioId: scenarioIds[0], @@ -60,6 +62,7 @@ describe(`when has two projects with scenarios and events`, () => { { kind: 'costSurface', status: 'running', + isoDate: expect.any(String), }, ], scenarioId: scenarioIds[1], diff --git a/api/apps/api/test/project-jobs-status/project-job-status.e2e-spec.ts b/api/apps/api/test/project-jobs-status/project-job-status.e2e-spec.ts index c6ecd9994e..002f98fc5b 100644 --- a/api/apps/api/test/project-jobs-status/project-job-status.e2e-spec.ts +++ b/api/apps/api/test/project-jobs-status/project-job-status.e2e-spec.ts @@ -1,6 +1,5 @@ import { INestApplication } from '@nestjs/common'; import { PromiseType } from 'utility-types'; -import * as request from 'supertest'; import { createWorld } from './world'; import { bootstrapApplication } from '../utils/api-application'; @@ -30,6 +29,7 @@ test(`job statuses for project`, async () => { data: null, kind: 'grid', status: 'running', + isoDate: expect.any(String), }, ]); expect(result.body.data.attributes.scenarios).toEqual([ @@ -39,6 +39,7 @@ test(`job statuses for project`, async () => { { kind: 'costSurface', status: 'done', + isoDate: expect.any(String), }, ], }, @@ -48,6 +49,7 @@ test(`job statuses for project`, async () => { { kind: 'planningUnitsInclusion', status: 'running', + isoDate: expect.any(String), }, ], },