diff --git a/package-lock.json b/package-lock.json index e17fbbfc..db26e166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2012,15 +2012,15 @@ } }, "@prisma/cli": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.4.1.tgz", - "integrity": "sha512-vAOBnouBgCYndXmTcGxanfmhVWUCpwr3akBXiQH+ZKzTYf/pwSsWYpeaXNQfVtUEJEQ0kumfLGdq6ZXnfX//aA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.8.1.tgz", + "integrity": "sha1-/f61aFfy+wjsl2lTcnl0V0nv/Pc=", "dev": true }, "@prisma/client": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.4.1.tgz", - "integrity": "sha512-13aphZb34ws8GuRxVsmgiQmUJRfcnnb5/Jy78/ZkpLyCk5ZpchO/Rr1KNiF/cfLHprdKOjSvUzthcx+tubaA2A==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.8.1.tgz", + "integrity": "sha1-b+h5aO7UKQHPdsYjmFIi68MYwpI=", "requires": { "pkg-up": "^3.1.0" } @@ -10737,7 +10737,7 @@ "pkg-up": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "integrity": "sha1-EA7CNcwVDk/UJRlBJZaihRKg3vU=", "requires": { "find-up": "^3.0.0" } diff --git a/package.json b/package.json index 326786e8..56669de8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@nestjs/platform-socket.io": "^7.4.2", "@nestjs/swagger": "^4.5.12", "@nestjs/websockets": "^7.4.2", - "@prisma/client": "^2.4.1", + "@prisma/client": "^2.8.1", "bcryptjs": "^2.4.3", "class-transformer": "^0.3.1", "class-validator": "^0.12.2", @@ -52,7 +52,7 @@ "@nestjs/cli": "^7.4.1", "@nestjs/schematics": "^7.0.1", "@nestjs/testing": "^7.4.2", - "@prisma/cli": "^2.4.1", + "@prisma/cli": "^2.8.1", "@types/bcryptjs": "^2.4.2", "@types/express": "^4.17.7", "@types/jest": "26.0.5", diff --git a/prisma/migrations/20201007145002-builds-counter/README.md b/prisma/migrations/20201007145002-builds-counter/README.md new file mode 100644 index 00000000..139cb517 --- /dev/null +++ b/prisma/migrations/20201007145002-builds-counter/README.md @@ -0,0 +1,91 @@ +# Migration `20201007145002-builds-counter` + +This migration has been generated by Pavel Strunkin at 10/7/2020, 5:50:02 PM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +ALTER TABLE "public"."Project" ADD COLUMN "buildsCounter" integer NOT NULL DEFAULT 0 +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration 20200909223305-test-variation-project-id-added-into-unique-constraint..20201007145002-builds-counter +--- datamodel.dml ++++ datamodel.dml +@@ -1,15 +1,16 @@ + generator client { + provider = "prisma-client-js" ++ previewFeatures = ["atomicNumberOperations"] + } + datasource db { +- provider = "postgresql" +- url = "***" ++ provider = "postgresql" ++ url = "***" + } + model Build { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + number Int? + branchName String? + status String? + testRuns TestRun[] +@@ -22,21 +23,22 @@ + isRunning Boolean? + } + model Project { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + name String + mainBranchName String @default("master") + builds Build[] ++ buildsCounter Int @default(0) + testVariations TestVariation[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + @@unique([name]) + } + model TestRun { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + imageName String + diffName String? + diffPercent Float? + diffTollerancePercent Float @default(0) +@@ -63,9 +65,9 @@ + baselineBranchName String? + } + model TestVariation { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + name String + branchName String @default("master") + browser String? + device String? +@@ -84,9 +86,9 @@ + @@unique([projectId, name, browser, device, os, viewport, branchName]) + } + model Baseline { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + baselineName String + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + testRunId String? +@@ -95,9 +97,9 @@ + createdAt DateTime @default(now()) + } + model User { +- id String @default(uuid()) @id ++ id String @id @default(uuid()) + email String @unique + password String + firstName String? + lastName String? +``` + + diff --git a/prisma/migrations/20201007145002-builds-counter/schema.prisma b/prisma/migrations/20201007145002-builds-counter/schema.prisma new file mode 100644 index 00000000..46e0624f --- /dev/null +++ b/prisma/migrations/20201007145002-builds-counter/schema.prisma @@ -0,0 +1,119 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["atomicNumberOperations"] +} + +datasource db { + provider = "postgresql" + url = "***" +} + +model Build { + id String @id @default(uuid()) + number Int? + branchName String? + status String? + testRuns TestRun[] + projectId String + project Project @relation(fields: [projectId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id]) + userId String? + isRunning Boolean? +} + +model Project { + id String @id @default(uuid()) + name String + mainBranchName String @default("master") + builds Build[] + buildsCounter Int @default(0) + testVariations TestVariation[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@unique([name]) +} + +model TestRun { + id String @id @default(uuid()) + imageName String + diffName String? + diffPercent Float? + diffTollerancePercent Float @default(0) + pixelMisMatchCount Int? + status TestStatus + buildId String + build Build @relation(fields: [buildId], references: [id]) + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + merge Boolean @default(false) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + // Test variation data + name String @default("") + browser String? + device String? + os String? + viewport String? + baselineName String? + ignoreAreas String @default("[]") + comment String? + baseline Baseline? + branchName String @default("master") + baselineBranchName String? +} + +model TestVariation { + id String @id @default(uuid()) + name String + branchName String @default("master") + browser String? + device String? + os String? + viewport String? + baselineName String? + ignoreAreas String @default("[]") + projectId String + project Project @relation(fields: [projectId], references: [id]) + testRuns TestRun[] + baselines Baseline[] + comment String? + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@unique([projectId, name, browser, device, os, viewport, branchName]) +} + +model Baseline { + id String @id @default(uuid()) + baselineName String + testVariationId String + testVariation TestVariation @relation(fields: [testVariationId], references: [id]) + testRunId String? + testRun TestRun? @relation(fields: [testRunId], references: [id]) + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +model User { + id String @id @default(uuid()) + email String @unique + password String + firstName String? + lastName String? + apiKey String @unique + isActive Boolean @default(true) + builds Build[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) +} + +enum TestStatus { + failed + new + ok + unresolved + approved +} diff --git a/prisma/migrations/20201007145002-builds-counter/steps.json b/prisma/migrations/20201007145002-builds-counter/steps.json new file mode 100644 index 00000000..f42782aa --- /dev/null +++ b/prisma/migrations/20201007145002-builds-counter/steps.json @@ -0,0 +1,37 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateField", + "model": "Project", + "field": "buildsCounter", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Project", + "field": "buildsCounter" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Project", + "field": "buildsCounter" + }, + "directive": "default" + }, + "argument": "", + "value": "0" + } + ] +} \ No newline at end of file diff --git a/prisma/migrations/migrate.lock b/prisma/migrations/migrate.lock index 1be6e40b..6f8e22c5 100644 --- a/prisma/migrations/migrate.lock +++ b/prisma/migrations/migrate.lock @@ -8,4 +8,5 @@ 20200715232608-branch-strategy 20200728221159-zero-diff-tolerance 20200812213545-build-run-status -20200909223305-test-variation-project-id-added-into-unique-constraint \ No newline at end of file +20200909223305-test-variation-project-id-added-into-unique-constraint +20201007145002-builds-counter \ No newline at end of file diff --git a/prisma/package-lock.json b/prisma/package-lock.json index 5e155d7d..f202b0de 100644 --- a/prisma/package-lock.json +++ b/prisma/package-lock.json @@ -5,15 +5,15 @@ "requires": true, "dependencies": { "@prisma/cli": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.4.1.tgz", - "integrity": "sha512-vAOBnouBgCYndXmTcGxanfmhVWUCpwr3akBXiQH+ZKzTYf/pwSsWYpeaXNQfVtUEJEQ0kumfLGdq6ZXnfX//aA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.8.1.tgz", + "integrity": "sha1-/f61aFfy+wjsl2lTcnl0V0nv/Pc=", "dev": true }, "@prisma/client": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.4.1.tgz", - "integrity": "sha512-13aphZb34ws8GuRxVsmgiQmUJRfcnnb5/Jy78/ZkpLyCk5ZpchO/Rr1KNiF/cfLHprdKOjSvUzthcx+tubaA2A==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.8.1.tgz", + "integrity": "sha1-b+h5aO7UKQHPdsYjmFIi68MYwpI=", "requires": { "pkg-up": "^3.1.0" } @@ -71,7 +71,7 @@ "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "integrity": "sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=", "requires": { "locate-path": "^3.0.0" } @@ -79,7 +79,7 @@ "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "integrity": "sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=", "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -94,7 +94,7 @@ "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "integrity": "sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=", "requires": { "p-try": "^2.0.0" } @@ -102,7 +102,7 @@ "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "integrity": "sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=", "requires": { "p-limit": "^2.0.0" } @@ -110,7 +110,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=" }, "path-exists": { "version": "3.0.0", @@ -120,7 +120,7 @@ "pkg-up": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "integrity": "sha1-EA7CNcwVDk/UJRlBJZaihRKg3vU=", "requires": { "find-up": "^3.0.0" } diff --git a/prisma/package.json b/prisma/package.json index e15fc8eb..b83161ea 100644 --- a/prisma/package.json +++ b/prisma/package.json @@ -7,12 +7,12 @@ "license": "UNLICENSED", "scripts": {}, "dependencies": { - "@prisma/client": "^2.2.2", + "@prisma/client": "^2.8.1", "bcryptjs": "^2.4.3", "uuid-apikey": "^1.4.6" }, "devDependencies": { - "@prisma/cli": "^2.4.1", + "@prisma/cli": "^2.8.1", "@types/bcryptjs": "^2.4.2", "@types/uuid-apikey": "^1.4.0", "ts-node": "^8.10.2", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 58d2da6c..db598486 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,14 +1,15 @@ generator client { provider = "prisma-client-js" + previewFeatures = ["atomicNumberOperations"] } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") } model Build { - id String @default(uuid()) @id + id String @id @default(uuid()) number Int? branchName String? status String? @@ -23,10 +24,11 @@ model Build { } model Project { - id String @default(uuid()) @id + id String @id @default(uuid()) name String mainBranchName String @default("master") builds Build[] + buildsCounter Int @default(0) testVariations TestVariation[] updatedAt DateTime @updatedAt createdAt DateTime @default(now()) @@ -35,7 +37,7 @@ model Project { } model TestRun { - id String @default(uuid()) @id + id String @id @default(uuid()) imageName String diffName String? diffPercent Float? @@ -64,7 +66,7 @@ model TestRun { } model TestVariation { - id String @default(uuid()) @id + id String @id @default(uuid()) name String branchName String @default("master") browser String? @@ -85,7 +87,7 @@ model TestVariation { } model Baseline { - id String @default(uuid()) @id + id String @id @default(uuid()) baselineName String testVariationId String testVariation TestVariation @relation(fields: [testVariationId], references: [id]) @@ -96,7 +98,7 @@ model Baseline { } model User { - id String @default(uuid()) @id + id String @id @default(uuid()) email String @unique password String firstName String? diff --git a/src/builds/builds.service.spec.ts b/src/builds/builds.service.spec.ts index 249c123b..d4b53a66 100644 --- a/src/builds/builds.service.spec.ts +++ b/src/builds/builds.service.spec.ts @@ -21,6 +21,7 @@ const initService = async ({ eventsBuildCreatedMock = jest.fn(), eventsBuildFinishedMock = jest.fn(), projectFindOneMock = jest.fn(), + projectUpdateMock = jest.fn(), }) => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -30,6 +31,7 @@ const initService = async ({ useValue: { project: { findOne: projectFindOneMock, + update: projectUpdateMock, }, build: { findMany: buildFindManyMock, @@ -144,24 +146,35 @@ describe('BuildsService', () => { id: 'project id', name: 'name', mainBranchName: 'master', + buildsCounter: 1, updatedAt: new Date(), createdAt: new Date(), }; const buildCreateMock = jest.fn().mockResolvedValueOnce(build); const projectFindOneMock = jest.fn().mockResolvedValueOnce(project); + const projectUpdateMock = jest.fn().mockResolvedValueOnce(project); const eventsBuildCreatedMock = jest.fn(); mocked(BuildDto).mockReturnValueOnce(buildDto); - service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock }); + service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock, projectUpdateMock }); const result = await service.create(createBuildDto); expect(projectFindOneMock).toHaveBeenCalledWith({ - where: { name: createBuildDto.project }, + where: { id: undefined, name: createBuildDto.project }, + }); + expect(projectUpdateMock).toHaveBeenCalledWith({ + where: { id: project.id }, + data: { + buildsCounter: { + increment: 1, + }, + }, }); expect(buildCreateMock).toHaveBeenCalledWith({ data: { branchName: createBuildDto.branchName, isRunning: true, + number: project.buildsCounter, project: { connect: { id: project.id, @@ -182,25 +195,36 @@ describe('BuildsService', () => { const project: Project = { id: '6bdd3704-90af-4b1b-94cb-f183e500f5cb', name: 'name', + buildsCounter: 1, mainBranchName: 'master', updatedAt: new Date(), createdAt: new Date(), }; const buildCreateMock = jest.fn().mockResolvedValueOnce(build); const projectFindOneMock = jest.fn().mockResolvedValueOnce(project); + const projectUpdateMock = jest.fn().mockResolvedValueOnce(project); const eventsBuildCreatedMock = jest.fn(); mocked(BuildDto).mockReturnValueOnce(buildDto); - service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock }); + service = await initService({ buildCreateMock, eventsBuildCreatedMock, projectFindOneMock, projectUpdateMock }); const result = await service.create(createBuildDto); expect(projectFindOneMock).toHaveBeenCalledWith({ - where: { id: createBuildDto.project }, + where: { id: createBuildDto.project, name: undefined }, + }); + expect(projectUpdateMock).toHaveBeenCalledWith({ + where: { id: project.id }, + data: { + buildsCounter: { + increment: 1, + }, + }, }); expect(buildCreateMock).toHaveBeenCalledWith({ data: { branchName: createBuildDto.branchName, isRunning: true, + number: project.buildsCounter, project: { connect: { id: project.id, diff --git a/src/builds/builds.service.ts b/src/builds/builds.service.ts index 82320995..28eb367d 100644 --- a/src/builds/builds.service.ts +++ b/src/builds/builds.service.ts @@ -28,28 +28,36 @@ export class BuildsService { } async create(createBuildDto: CreateBuildDto): Promise { - let project: Project; - - if (uuidAPIKey.isUUID(createBuildDto.project)) { - project = await this.prismaService.project.findOne({ - where: { id: createBuildDto.project }, - }); - } else { - project = await this.prismaService.project.findOne({ - where: { - name: createBuildDto.project, - }, - }); - } - + // find project + const isUUID = uuidAPIKey.isUUID(createBuildDto.project); + let project: Project = await this.prismaService.project.findOne({ + where: { + id: isUUID ? createBuildDto.project : undefined, + name: !isUUID ? createBuildDto.project : undefined, + }, + }); if (!project) { throw new HttpException(`Project not found`, HttpStatus.NOT_FOUND); } + // increment build number + project = await this.prismaService.project.update({ + where: { + id: project.id, + }, + data: { + buildsCounter: { + increment: 1, + }, + }, + }); + + // create build const build = await this.prismaService.build.create({ data: { branchName: createBuildDto.branchName, isRunning: true, + number: project.buildsCounter, project: { connect: { id: project.id, diff --git a/src/projects/dto/project.dto.ts b/src/projects/dto/project.dto.ts index 301beb64..f23ca029 100644 --- a/src/projects/dto/project.dto.ts +++ b/src/projects/dto/project.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID, IsString } from 'class-validator'; +import { IsUUID, IsString, IsNumber } from 'class-validator'; import { Project } from '@prisma/client'; export class ProjectDto implements Project { @@ -7,6 +7,10 @@ export class ProjectDto implements Project { @IsUUID() readonly id: string; + @ApiProperty() + @IsNumber() + readonly buildsCounter: number; + @ApiProperty() @IsString() readonly name: string; diff --git a/src/test-variations/test-variations.service.spec.ts b/src/test-variations/test-variations.service.spec.ts index b42c010e..c7b4b6a9 100644 --- a/src/test-variations/test-variations.service.spec.ts +++ b/src/test-variations/test-variations.service.spec.ts @@ -109,6 +109,7 @@ describe('TestVariationsService', () => { const projectMock: Project = { id: '12', name: 'Project', + buildsCounter: 0, mainBranchName: 'master', createdAt: new Date(), updatedAt: new Date(), @@ -323,6 +324,7 @@ describe('TestVariationsService', () => { const mergedBranch = 'develop'; const project: Project = { id: 'some id', + buildsCounter: 0, name: 'some name', mainBranchName: 'master', updatedAt: new Date(),