Skip to content

Commit cc915df

Browse files
authoredSep 5, 2023
feat(2911): Job Templates - Pipeline Usage Report (#2912)
1 parent 1f56b06 commit cc915df

8 files changed

+246
-66
lines changed
 

‎.prettierrc.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
trailingComma: "none"
2+
printWidth: 110
3+
singleQuote: true
4+
tabWidth: 4
5+
arrowParens: "avoid"
6+
htmlWhitespaceSensitivity: "strict"

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,12 @@
138138
}
139139
},
140140
"devDependencies": {
141+
"@cucumber/cucumber": "^9.0.0",
141142
"@octokit/rest": "^19.0.5",
142143
"chai": "^4.3.7",
143144
"chai-as-promised": "^7.1.1",
144145
"chai-jwt": "^2.0.0",
145146
"coveralls": "^3.1.1",
146-
"@cucumber/cucumber": "^9.0.0",
147147
"eslint": "^8.28.0",
148148
"eslint-config-screwdriver": "^7.0.0",
149149
"form-data": "^4.0.0",

‎plugins/templates/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,38 @@ If the deleted version was the last published, the API would set the `latest` at
131131
* `name` - Name of the template
132132
* `version` - Version of the template
133133

134+
##### Get Report of Pipelines that use a template version
135+
136+
`GET /templates/{name}/{version}/usage/pipelines`
137+
138+
139+
###### Arguments
140+
141+
'name', 'version'
142+
143+
* `name` - Name of the template
144+
* `version` - Version of the template
145+
146+
###### Example response
147+
148+
```json
149+
[
150+
{
151+
id: 4,
152+
name: 'nathom/sd-uses-template',
153+
scmRepo: {
154+
branch: 'main',
155+
name: 'nathom/sd-uses-template',
156+
url: 'https://github.com/nathom/sd-uses-template/tree/main/pipe2',
157+
rootDir: 'pipe2',
158+
private: false
159+
},
160+
lastRun: '2023-07-31T17:15:37.510Z',
161+
admins: { nathom: true }
162+
},
163+
]
164+
```
165+
134166
#### Template Tag
135167
Template tag allows fetching on template version by tag. For example, tag `mytemplate@1.1.0` as `stable`.
136168

‎plugins/templates/getPipelineUsage.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const boom = require('@hapi/boom');
4+
const joi = require('joi');
5+
const schema = require('screwdriver-data-schema');
6+
const nameSchema = schema.models.template.base.extract('name');
7+
const versionSchema = schema.models.template.base.extract('version');
8+
const tagSchema = schema.models.templateTag.base.extract('tag');
9+
const getSchema = schema.api.pipelineUsage.get;
10+
11+
module.exports = () => ({
12+
method: 'GET',
13+
path: '/templates/{name}/{versionOrTag}/usage/pipelines',
14+
options: {
15+
description: 'Get information for the pipelines that are being used by a specific template version.',
16+
notes: 'Returns information aboout the pipelines using the template version.',
17+
tags: ['api', 'templates', 'pipelines', 'metrics'],
18+
auth: {
19+
strategies: ['token'],
20+
scope: ['user', 'build']
21+
},
22+
23+
handler: async (request, h) => {
24+
const { templateFactory } = request.server.app;
25+
const { name, versionOrTag } = request.params;
26+
27+
return templateFactory
28+
.getPipelineUsage(`${name}@${versionOrTag}`)
29+
.then(pipelines => {
30+
return h.response(pipelines);
31+
})
32+
.catch(err => {
33+
if (err.message === 'Template does not exist') {
34+
throw boom.notFound(`Template ${name}@${versionOrTag} does not exist`);
35+
} else {
36+
throw err;
37+
}
38+
});
39+
},
40+
response: {
41+
schema: getSchema
42+
},
43+
validate: {
44+
params: joi.object({
45+
name: nameSchema,
46+
versionOrTag: joi.alternatives().try(versionSchema, tagSchema)
47+
})
48+
}
49+
}
50+
});

‎plugins/templates/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const removeTagRoute = require('./removeTag');
1313
const removeVersionRoute = require('./removeVersion');
1414
const updateTrustedRoute = require('./updateTrusted');
1515
const getTemplateByIdRoute = require('./getTemplateById');
16+
const getPipelineUsage = require('./getPipelineUsage');
1617

1718
/**
1819
* Template API Plugin
@@ -87,7 +88,8 @@ const templatesPlugin = {
8788
removeTagRoute(),
8889
removeVersionRoute(),
8990
updateTrustedRoute(),
90-
getTemplateByIdRoute()
91+
getTemplateByIdRoute(),
92+
getPipelineUsage()
9193
]);
9294
}
9395
};

‎test/plugins/data/pipelineUsage.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"id": 4,
4+
"name": "screwdriver/build",
5+
"scmRepo": {
6+
"branch": "main",
7+
"name": "screwdriver/build",
8+
"url": "https://github.com/nathom/sd-uses-template/tree/main/pipe2",
9+
"rootDir": "pipe2",
10+
"private": false
11+
},
12+
"lastRun": "2023-07-31T17:15:37.510Z",
13+
"admins": { "nathom": true }
14+
}
15+
]
+80-61
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,83 @@
1-
[{
2-
"id": 7969,
3-
"name": "screwdriver/build",
4-
"labels": ["stable"],
5-
"version": "0.0.1",
6-
"description": "this is a template to test",
7-
"maintainer": "foo@bar.com",
8-
"pipelineId": 8765,
9-
"config": {
10-
"steps": [{
11-
"echo": "echo hello"
12-
}]
13-
},
14-
"metrics": {
15-
"jobs": {
16-
"count": 2
1+
[
2+
{
3+
"id": 7969,
4+
"name": "screwdriver/build",
5+
"labels": ["stable"],
6+
"version": "0.0.1",
7+
"description": "this is a template to test",
8+
"maintainer": "foo@bar.com",
9+
"pipelineId": 8765,
10+
"config": {
11+
"steps": [
12+
{
13+
"echo": "echo hello"
14+
}
15+
]
16+
},
17+
"metrics": {
18+
"jobs": {
19+
"count": 2
20+
},
21+
"builds": {
22+
"count": 7
23+
},
24+
"pipelines": {
25+
"count": 1
26+
}
27+
}
1728
},
18-
"builds": {
19-
"count": 7
20-
}
21-
}
22-
},{
23-
"id": 7970,
24-
"name": "screwdriver/build",
25-
"labels": ["stable"],
26-
"version": "1.0.2",
27-
"description": "this is a template to test",
28-
"maintainer": "foo@bar.com",
29-
"pipelineId": 8765,
30-
"config": {
31-
"steps": [{
32-
"echo": "echo hello"
33-
}]
34-
},
35-
"metrics": {
36-
"jobs": {
37-
"count": 1
38-
},
39-
"builds": {
40-
"count": 9
41-
}
42-
}
43-
}, {
44-
"id": 7971,
45-
"name": "screwdriver/build",
46-
"labels": ["stable"],
47-
"version": "3.2.1",
48-
"description": "this is a template to test",
49-
"maintainer": "foo@bar.com",
50-
"pipelineId": 8765,
51-
"config": {
52-
"steps": [{
53-
"echo": "echo hello"
54-
}]
55-
},
56-
"metrics": {
57-
"jobs": {
58-
"count": 5
29+
{
30+
"id": 7970,
31+
"name": "screwdriver/build",
32+
"labels": ["stable"],
33+
"version": "1.0.2",
34+
"description": "this is a template to test",
35+
"maintainer": "foo@bar.com",
36+
"pipelineId": 8765,
37+
"config": {
38+
"steps": [
39+
{
40+
"echo": "echo hello"
41+
}
42+
]
43+
},
44+
"metrics": {
45+
"jobs": {
46+
"count": 1
47+
},
48+
"builds": {
49+
"count": 9
50+
},
51+
"pipelines": {
52+
"count": 1
53+
}
54+
}
5955
},
60-
"builds": {
61-
"count": 13
56+
{
57+
"id": 7971,
58+
"name": "screwdriver/build",
59+
"labels": ["stable"],
60+
"version": "3.2.1",
61+
"description": "this is a template to test",
62+
"maintainer": "foo@bar.com",
63+
"pipelineId": 8765,
64+
"config": {
65+
"steps": [
66+
{
67+
"echo": "echo hello"
68+
}
69+
]
70+
},
71+
"metrics": {
72+
"jobs": {
73+
"count": 5
74+
},
75+
"builds": {
76+
"count": 13
77+
},
78+
"pipelines": {
79+
"count": 3
80+
}
81+
}
6282
}
63-
}
64-
}]
83+
]

‎test/plugins/templates.test.js

+60-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const testtemplateversions = require('./data/templateVersions.json');
1212
const testTemplateVersionsMetrics = require('./data/templateVersionsMetrics.json');
1313
const testTemplateWithNamespace = require('./data/templateWithNamespace.json');
1414
const testpipeline = require('./data/pipeline.json');
15+
const testPipelineUsage = require('./data/pipelineUsage.json');
1516
const TEMPLATE_INVALID = require('./data/template-validator.missing-version.json');
1617
const TEMPLATE_VALID = require('./data/template-validator.input.json');
1718
const TEMPLATE_VALID_NEW_VERSION = require('./data/template-create.input.json');
@@ -69,7 +70,8 @@ describe('template plugin test', () => {
6970
list: sinon.stub(),
7071
listWithMetrics: sinon.stub(),
7172
getTemplate: sinon.stub(),
72-
get: sinon.stub()
73+
get: sinon.stub(),
74+
getPipelineUsage: sinon.stub()
7375
};
7476
templateTagFactoryMock = {
7577
create: sinon.stub(),
@@ -432,6 +434,55 @@ describe('template plugin test', () => {
432434
});
433435
});
434436

437+
describe('GET /templates/name/versionOrTag/usage/pipelines', () => {
438+
let options;
439+
440+
beforeEach(() => {
441+
options = {
442+
method: 'GET',
443+
url: '/templates/screwdriver%2Fbuild/1.7.3/usage/pipelines'
444+
};
445+
});
446+
447+
it('returns 200 and all pipelines that use the template version', () => {
448+
templateFactoryMock.getPipelineUsage.resolves(testPipelineUsage);
449+
450+
return server.inject(options).then(reply => {
451+
assert.deepEqual(reply.result, testPipelineUsage);
452+
assert.equal(reply.statusCode, 200);
453+
assert.calledWith(templateFactoryMock.getPipelineUsage, `screwdriver/build@1.7.3`);
454+
});
455+
});
456+
457+
it('returns 200 even if no pipelines are using the template version', () => {
458+
templateFactoryMock.getPipelineUsage.resolves([]);
459+
460+
return server.inject(options).then(reply => {
461+
assert.deepEqual(reply.result, []);
462+
assert.equal(reply.statusCode, 200);
463+
assert.calledWith(templateFactoryMock.getPipelineUsage, `screwdriver/build@1.7.3`);
464+
});
465+
});
466+
467+
it('returns 404 when the template does not exist', () => {
468+
templateFactoryMock.getPipelineUsage.returns(Promise.reject(new Error('Template does not exist')));
469+
const error = {
470+
statusCode: 404,
471+
error: 'Not Found',
472+
message: 'Template screwdriver/build@1.7.3 does not exist'
473+
};
474+
475+
return server.inject(options).then(reply => {
476+
const payload = JSON.parse(reply.payload);
477+
478+
Object.keys(error).forEach(k => {
479+
assert.equal(payload[k], error[k]);
480+
});
481+
assert.calledWith(templateFactoryMock.getPipelineUsage, `screwdriver/build@1.7.3`);
482+
});
483+
});
484+
});
485+
435486
describe('GET /templates/name/metrics', () => {
436487
let options;
437488

@@ -1241,7 +1292,10 @@ describe('template plugin test', () => {
12411292

12421293
templateFactoryMock.get.resolves(testTemplateV1);
12431294
templateFactoryMock.get
1244-
.withArgs({ name: `${templateNameSpace}/${templateName}`, version: templateVersion1 })
1295+
.withArgs({
1296+
name: `${templateNameSpace}/${templateName}`,
1297+
version: templateVersion1
1298+
})
12451299
.resolves(testTemplateV1);
12461300
templateTagFactoryMock.list.resolves([testTemplateTag]);
12471301
});
@@ -1274,7 +1328,10 @@ describe('template plugin test', () => {
12741328
return server.inject(options).then(reply => {
12751329
assert.equal(reply.statusCode, error.statusCode);
12761330
assert.deepEqual(reply.result, error);
1277-
assert.calledWith(templateFactoryMock.get, { name: 'test-namespace/test-template', version: '1.0.0' });
1331+
assert.calledWith(templateFactoryMock.get, {
1332+
name: 'test-namespace/test-template',
1333+
version: '1.0.0'
1334+
});
12781335
});
12791336
});
12801337

0 commit comments

Comments
 (0)
Please sign in to comment.