Skip to content

Commit d8b284a

Browse files
authored
Issue 135 : Added ability to limit number of builds to keep (just like Jenkins) (#142)
1 parent 43a467e commit d8b284a

File tree

13 files changed

+281
-19
lines changed

13 files changed

+281
-19
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Migration `20210612140950-limit-build-number`
2+
3+
This migration has been generated by Pavel Strunkin at 6/12/2021, 5:09:50 PM.
4+
You can check out the [state of the schema](./schema.prisma) after the migration.
5+
6+
## Database Steps
7+
8+
```sql
9+
ALTER TABLE "Project" ADD COLUMN "maxBuildAllowed" INTEGER NOT NULL DEFAULT 100
10+
```
11+
12+
## Changes
13+
14+
```diff
15+
diff --git schema.prisma schema.prisma
16+
migration 20210605124856-image-compare-config-as-json..20210612140950-limit-build-number
17+
--- datamodel.dml
18+
+++ datamodel.dml
19+
@@ -3,9 +3,9 @@
20+
}
21+
datasource db {
22+
provider = "postgresql"
23+
- url = "***"
24+
+ url = "***"
25+
}
26+
model Build {
27+
id String @id @default(uuid())
28+
@@ -30,8 +30,9 @@
29+
name String
30+
mainBranchName String @default("master")
31+
builds Build[]
32+
buildsCounter Int @default(0)
33+
+ maxBuildAllowed Int @default(100)
34+
testVariations TestVariation[]
35+
updatedAt DateTime @updatedAt
36+
createdAt DateTime @default(now())
37+
// config
38+
```
39+
40+
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
generator client {
2+
provider = "prisma-client-js"
3+
}
4+
5+
datasource db {
6+
provider = "postgresql"
7+
url = "***"
8+
}
9+
10+
model Build {
11+
id String @id @default(uuid())
12+
ciBuildId String?
13+
number Int?
14+
branchName String?
15+
status String?
16+
testRuns TestRun[]
17+
projectId String
18+
project Project @relation(fields: [projectId], references: [id])
19+
updatedAt DateTime @updatedAt
20+
createdAt DateTime @default(now())
21+
user User? @relation(fields: [userId], references: [id])
22+
userId String?
23+
isRunning Boolean?
24+
25+
@@unique([projectId, ciBuildId])
26+
}
27+
28+
model Project {
29+
id String @id @default(uuid())
30+
name String
31+
mainBranchName String @default("master")
32+
builds Build[]
33+
buildsCounter Int @default(0)
34+
maxBuildAllowed Int @default(100)
35+
testVariations TestVariation[]
36+
updatedAt DateTime @updatedAt
37+
createdAt DateTime @default(now())
38+
// config
39+
autoApproveFeature Boolean @default(false)
40+
imageComparison ImageComparison @default(pixelmatch)
41+
imageComparisonConfig String @default("{ \"threshold\": 0.1, \"ignoreAntialiasing\": true, \"allowDiffDimensions\": false }")
42+
43+
@@unique([name])
44+
}
45+
46+
model TestRun {
47+
id String @id @default(uuid())
48+
imageName String
49+
diffName String?
50+
diffPercent Float?
51+
diffTollerancePercent Float @default(0)
52+
pixelMisMatchCount Int?
53+
status TestStatus
54+
buildId String
55+
build Build @relation(fields: [buildId], references: [id])
56+
testVariationId String?
57+
testVariation TestVariation? @relation(fields: [testVariationId], references: [id])
58+
merge Boolean @default(false)
59+
updatedAt DateTime @updatedAt
60+
createdAt DateTime @default(now())
61+
// Test variation data
62+
name String @default("")
63+
browser String?
64+
device String?
65+
os String?
66+
viewport String?
67+
customTags String? @default("")
68+
baselineName String?
69+
comment String?
70+
baseline Baseline?
71+
branchName String @default("master")
72+
baselineBranchName String?
73+
ignoreAreas String @default("[]")
74+
tempIgnoreAreas String @default("[]")
75+
}
76+
77+
model TestVariation {
78+
id String @id @default(uuid())
79+
name String
80+
branchName String @default("master")
81+
browser String @default("")
82+
device String @default("")
83+
os String @default("")
84+
viewport String @default("")
85+
customTags String @default("")
86+
baselineName String?
87+
ignoreAreas String @default("[]")
88+
projectId String
89+
project Project @relation(fields: [projectId], references: [id])
90+
testRuns TestRun[]
91+
baselines Baseline[]
92+
comment String?
93+
updatedAt DateTime @updatedAt
94+
createdAt DateTime @default(now())
95+
96+
@@unique([projectId, name, browser, device, os, viewport, customTags, branchName])
97+
}
98+
99+
model Baseline {
100+
id String @id @default(uuid())
101+
baselineName String
102+
testVariationId String
103+
testVariation TestVariation @relation(fields: [testVariationId], references: [id])
104+
testRunId String?
105+
testRun TestRun? @relation(fields: [testRunId], references: [id])
106+
updatedAt DateTime @updatedAt
107+
createdAt DateTime @default(now())
108+
}
109+
110+
model User {
111+
id String @id @default(uuid())
112+
email String @unique
113+
password String
114+
firstName String?
115+
lastName String?
116+
apiKey String @unique
117+
isActive Boolean @default(true)
118+
builds Build[]
119+
updatedAt DateTime @updatedAt
120+
createdAt DateTime @default(now())
121+
}
122+
123+
enum TestStatus {
124+
failed
125+
new
126+
ok
127+
unresolved
128+
approved
129+
autoApproved
130+
}
131+
132+
enum ImageComparison {
133+
pixelmatch
134+
lookSame
135+
odiff
136+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"version": "0.3.14-fixed",
3+
"steps": [
4+
{
5+
"tag": "CreateField",
6+
"model": "Project",
7+
"field": "maxBuildAllowed",
8+
"type": "Int",
9+
"arity": "Required"
10+
},
11+
{
12+
"tag": "CreateDirective",
13+
"location": {
14+
"path": {
15+
"tag": "Field",
16+
"model": "Project",
17+
"field": "maxBuildAllowed"
18+
},
19+
"directive": "default"
20+
}
21+
},
22+
{
23+
"tag": "CreateArgument",
24+
"location": {
25+
"tag": "Directive",
26+
"path": {
27+
"tag": "Field",
28+
"model": "Project",
29+
"field": "maxBuildAllowed"
30+
},
31+
"directive": "default"
32+
},
33+
"argument": "",
34+
"value": "100"
35+
}
36+
]
37+
}

prisma/migrations/migrate.lock

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@
1818
20210405171118-github_243-set-empty-test-variation-tags-instead-of-null
1919
20210425191116-github_215_project_config
2020
20210517203552-add-custom-tags
21-
20210605124856-image-compare-config-as-json
21+
20210605124856-image-compare-config-as-json
22+
20210612140950-limit-build-number

prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ model Project {
3131
mainBranchName String @default("master")
3232
builds Build[]
3333
buildsCounter Int @default(0)
34+
maxBuildAllowed Int @default(100)
3435
testVariations TestVariation[]
3536
updatedAt DateTime @updatedAt
3637
createdAt DateTime @default(now())

src/_data_/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const TEST_PROJECT: Project = {
44
id: '1',
55
name: 'Test Project',
66
buildsCounter: 2,
7+
maxBuildAllowed: 100,
78
mainBranchName: 'master',
89
createdAt: new Date(),
910
updatedAt: new Date(),

src/builds/builds.controller.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const initController = async ({
1616
buildFindOrCreateMock = jest.fn(),
1717
buildIncrementBuildNumberMock = jest.fn(),
1818
eventBuildCreatedMock = jest.fn(),
19+
deleteOldBuilds = jest.fn(),
1920
}) => {
2021
const module: TestingModule = await Test.createTestingModule({
2122
controllers: [BuildsController],
@@ -31,6 +32,7 @@ const initController = async ({
3132
useValue: {
3233
findOrCreate: buildFindOrCreateMock,
3334
incrementBuildNumber: buildIncrementBuildNumberMock,
35+
deleteOldBuilds: deleteOldBuilds,
3436
},
3537
},
3638
{ provide: EventsGateway, useValue: { buildCreated: eventBuildCreatedMock } },
@@ -60,7 +62,7 @@ describe('Builds Controller', () => {
6062
number: 12,
6163
};
6264

63-
beforeEach(async () => {});
65+
beforeEach(async () => { });
6466

6567
it('should be defined', async () => {
6668
controller = await initController({});
@@ -72,16 +74,19 @@ describe('Builds Controller', () => {
7274
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
7375
const buildFindOrCreateMock = jest.fn().mockResolvedValueOnce(newBuild);
7476
const buildIncrementBuildNumberMock = jest.fn().mockResolvedValueOnce(buildWithNumber);
77+
const deleteOldBuilds = jest.fn();
7578
controller = await initController({
7679
projectFindOneMock,
7780
buildFindOrCreateMock,
7881
buildIncrementBuildNumberMock,
7982
eventBuildCreatedMock,
83+
deleteOldBuilds,
8084
});
8185

8286
const result = await controller.create(createBuildDto);
8387

8488
expect(result).toStrictEqual(new BuildDto(buildWithNumber));
89+
expect(deleteOldBuilds).toHaveBeenCalledWith(project.id, project.maxBuildAllowed);
8590
expect(buildIncrementBuildNumberMock).toHaveBeenCalledWith(newBuild.id, project.id);
8691
expect(eventBuildCreatedMock).toHaveBeenCalledWith(new BuildDto(buildWithNumber));
8792
});
@@ -90,10 +95,12 @@ describe('Builds Controller', () => {
9095
const eventBuildCreatedMock = jest.fn();
9196
const projectFindOneMock = jest.fn().mockResolvedValueOnce(project);
9297
const buildFindOrCreateMock = jest.fn().mockResolvedValueOnce(buildWithNumber);
98+
const deleteOldBuilds = jest.fn();
9399
controller = await initController({
94100
projectFindOneMock,
95101
buildFindOrCreateMock,
96102
eventBuildCreatedMock,
103+
deleteOldBuilds,
97104
});
98105

99106
const result = await controller.create(createBuildDto);

src/builds/builds.controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class BuildsController {
3535
private eventsGateway: EventsGateway,
3636
@Inject(forwardRef(() => ProjectsService))
3737
private projectService: ProjectsService
38-
) {}
38+
) { }
3939

4040
@Get()
4141
@ApiOkResponse({ type: PaginatedBuildDto })
@@ -70,6 +70,7 @@ export class BuildsController {
7070
@UseGuards(ApiGuard)
7171
async create(@Body() createBuildDto: CreateBuildDto): Promise<BuildDto> {
7272
const project = await this.projectService.findOne(createBuildDto.project);
73+
await this.buildsService.deleteOldBuilds(project.id, project.maxBuildAllowed);
7374
let build = await this.buildsService.findOrCreate({
7475
projectId: project.id,
7576
branchName: createBuildDto.branchName,

src/builds/builds.service.spec.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { BuildsService } from './builds.service';
33
import { PrismaService } from '../prisma/prisma.service';
44
import { TestRunsService } from '../test-runs/test-runs.service';
55
import { EventsGateway } from '../shared/events/events.gateway';
6-
import { CreateBuildDto } from './dto/build-create.dto';
7-
import { Build, TestRun, Project, TestStatus } from '@prisma/client';
6+
import { Build, TestRun, TestStatus } from '@prisma/client';
87
import { mocked } from 'ts-jest/utils';
98
import { BuildDto } from './dto/build.dto';
109
import { ProjectsService } from '../projects/projects.service';
@@ -24,6 +23,7 @@ const initService = async ({
2423
testRunFindManyMock = jest.fn(),
2524
eventsBuildUpdatedMock = jest.fn(),
2625
eventsBuildCreatedMock = jest.fn(),
26+
eventBuildDeletedMock = jest.fn(),
2727
projectFindOneMock = jest.fn(),
2828
projectUpdateMock = jest.fn(),
2929
}) => {
@@ -60,6 +60,7 @@ const initService = async ({
6060
useValue: {
6161
buildUpdated: eventsBuildUpdatedMock,
6262
buildCreated: eventsBuildCreatedMock,
63+
buildDeleted: eventBuildDeletedMock,
6364
},
6465
},
6566
{
@@ -108,7 +109,7 @@ describe('BuildsService', () => {
108109
device: null,
109110
os: null,
110111
viewport: '1800x1600',
111-
customTags:'',
112+
customTags: '',
112113
baselineName: null,
113114
ignoreAreas: '[]',
114115
tempIgnoreAreas: '[]',
@@ -177,9 +178,11 @@ describe('BuildsService', () => {
177178

178179
it('delete', async () => {
179180
const buildFindUniqueMock = jest.fn().mockResolvedValueOnce(build);
180-
const buildDeleteMock = jest.fn();
181+
const buildFindManyMock = jest.fn().mockImplementation(() => Promise.resolve(build));
182+
const buildDeleteMock = jest.fn().mockImplementation(() => Promise.resolve(build));
181183
const testRunDeleteMock = jest.fn();
182-
service = await initService({ buildFindUniqueMock, buildDeleteMock, testRunDeleteMock });
184+
const eventBuildDeletedMock = jest.fn();
185+
service = await initService({ buildFindUniqueMock, buildDeleteMock, testRunDeleteMock, eventBuildDeletedMock, buildFindManyMock });
183186

184187
await service.remove(build.id);
185188

@@ -190,6 +193,7 @@ describe('BuildsService', () => {
190193
},
191194
});
192195
expect(testRunDeleteMock).toHaveBeenCalledWith(build.testRuns[0].id);
196+
expect(eventBuildDeletedMock).toHaveBeenCalledWith(new BuildDto(build));
193197
expect(buildDeleteMock).toHaveBeenCalledWith({
194198
where: { id: build.id },
195199
});

0 commit comments

Comments
 (0)