refactor [Scrutinizer] (#3266)
* refactor(Scrutinizer): migrated to new BaseJsonService * refactor(ScrutinizerCoverage): updated color scale to match * refactor(Scrutinizer): switched to multiple classes to handle dif. git hosts * refactor(Scrutinizer): finished migrating to new service arch. * fix(Scrutinizer): fixed branch check logic * refactor(Scrutinizer): inline transforms based on PR feedback * refactor(ScrutinizerCoverage): change handling of no coverage scenario
This commit is contained in:
42
services/scrutinizer/scrutinizer-base.js
Normal file
42
services/scrutinizer/scrutinizer-base.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const { BaseJsonService, NotFound } = require('..')
|
||||
|
||||
module.exports = class ScrutinizerBase extends BaseJsonService {
|
||||
// https://scrutinizer-ci.com/docs/api/#repository-details
|
||||
async fetch({ schema, vcs, slug }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://scrutinizer-ci.com/api/repositories/${vcs}/${slug}`,
|
||||
errorMessages: {
|
||||
401: 'not authorized to access project',
|
||||
404: 'project not found',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
transformBranchInfo({ json, wantedBranch }) {
|
||||
if (!wantedBranch) {
|
||||
return json.applications[json.default_branch]
|
||||
}
|
||||
|
||||
const branch = json.applications[wantedBranch]
|
||||
if (!branch) {
|
||||
throw new NotFound({ prettyMessage: ' branch not found' })
|
||||
}
|
||||
|
||||
return branch
|
||||
}
|
||||
|
||||
transformBranchInfoMetricValue({ json, branch, metric }) {
|
||||
const {
|
||||
index: {
|
||||
_embedded: {
|
||||
project: { metric_values: metricValues },
|
||||
},
|
||||
},
|
||||
} = this.transformBranchInfo({ json, wantedBranch: branch })
|
||||
|
||||
return { value: metricValues[metric] }
|
||||
}
|
||||
}
|
||||
128
services/scrutinizer/scrutinizer-build.service.js
Normal file
128
services/scrutinizer/scrutinizer-build.service.js
Normal file
@@ -0,0 +1,128 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus, renderBuildStatusBadge } = require('../build-status')
|
||||
const ScrutinizerBase = require('./scrutinizer-base')
|
||||
|
||||
const schema = Joi.object({
|
||||
default_branch: Joi.string().required(),
|
||||
applications: Joi.object()
|
||||
.pattern(
|
||||
/^/,
|
||||
Joi.object({
|
||||
build_status: Joi.object({
|
||||
status: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
}).required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
class ScrutinizerBuildBase extends ScrutinizerBase {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'build',
|
||||
}
|
||||
}
|
||||
|
||||
async makeBadge({ vcs, slug, branch }) {
|
||||
const json = await this.fetch({ schema, vcs, slug })
|
||||
const {
|
||||
build_status: { status },
|
||||
} = this.transformBranchInfo({ json, wantedBranch: branch })
|
||||
return renderBuildStatusBadge({ status })
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerBuild extends ScrutinizerBuildBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/build',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer build (GitHub/Bitbucket)',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
vcs: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ vcs, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs,
|
||||
slug: `${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerGitLabBuild extends ScrutinizerBuildBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/build/gl',
|
||||
pattern: ':instance/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
// There are no known anonymous accessible Scrutinizer reports available for GitLab repos.
|
||||
// The example used is valid, but the project will not be accessible if Shields users try to use it.
|
||||
// https://gitlab.propertywindow.nl/propertywindow/client
|
||||
// https://scrutinizer-ci.com/gl/propertywindow/propertywindow/client/badges/quality-score.png?b=master&s=dfae6992a48184cc2333b4c349cec0447f0d67c2
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer build (GitLab)',
|
||||
pattern: ':instance/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
instance: 'propertywindow',
|
||||
user: 'propertywindow',
|
||||
repo: 'client',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ instance, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs: 'gl',
|
||||
slug: `${instance}/${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerPlainGitBuild extends ScrutinizerBuildBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/build/gp',
|
||||
pattern: ':slug/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ slug, branch }) {
|
||||
return this.makeBadge({ vcs: 'gp', slug, branch })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
ScrutinizerBuild,
|
||||
ScrutinizerGitLabBuild,
|
||||
ScrutinizerPlainGitBuild,
|
||||
]
|
||||
74
services/scrutinizer/scrutinizer-build.tester.js
Normal file
74
services/scrutinizer/scrutinizer-build.tester.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'ScrutinizerBuild',
|
||||
title: 'ScrutinizerBuild',
|
||||
pathPrefix: '/scrutinizer/build',
|
||||
}))
|
||||
|
||||
t.create('build (GitHub)')
|
||||
.get('/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
})
|
||||
|
||||
t.create('build (Bitbucket)')
|
||||
.get('/b/atlassian/python-bitbucket.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
})
|
||||
|
||||
t.create('build (branch)')
|
||||
.get('/g/phpmyadmin/phpmyadmin/master.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
})
|
||||
|
||||
t.create('build - unknown status')
|
||||
.get('/g/filp/whoops.json')
|
||||
.intercept(nock =>
|
||||
nock('https://scrutinizer-ci.com')
|
||||
.get('/api/repositories/g/filp/whoops')
|
||||
.reply(200, {
|
||||
default_branch: 'master',
|
||||
applications: {
|
||||
master: {
|
||||
build_status: {
|
||||
status: 'unknown',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'unknown',
|
||||
color: 'lightgrey',
|
||||
})
|
||||
|
||||
t.create('build private project')
|
||||
.get('/gl/propertywindow/propertywindow/client.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'not authorized to access project',
|
||||
})
|
||||
|
||||
t.create('build nonexistent project')
|
||||
.get('/gp/foo.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'project not found',
|
||||
})
|
||||
|
||||
t.create('build nonexistent branch')
|
||||
.get('/g/phpmyadmin/phpmyadmin/super-fake/not-real-branch.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'branch not found',
|
||||
})
|
||||
160
services/scrutinizer/scrutinizer-coverage.service.js
Normal file
160
services/scrutinizer/scrutinizer-coverage.service.js
Normal file
@@ -0,0 +1,160 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { NotFound } = require('..')
|
||||
const { colorScale } = require('../color-formatters')
|
||||
const ScrutinizerBase = require('./scrutinizer-base')
|
||||
|
||||
const schema = Joi.object({
|
||||
default_branch: Joi.string().required(),
|
||||
applications: Joi.object()
|
||||
.pattern(
|
||||
/^/,
|
||||
Joi.object({
|
||||
index: Joi.object({
|
||||
_embedded: Joi.object({
|
||||
project: Joi.object({
|
||||
metric_values: Joi.object({
|
||||
'scrutinizer.test_coverage': Joi.number().positive(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
// https://scrutinizer-ci.com/g/filp/whoops/code-structure/master/code-coverage
|
||||
// < 40% - red
|
||||
// 40-60% (inclusive) - yellow
|
||||
// > 60% brightgreen
|
||||
const scale = colorScale([40, 61], ['red', 'yellow', 'brightgreen'])
|
||||
|
||||
class ScrutinizerCoverageBase extends ScrutinizerBase {
|
||||
static get category() {
|
||||
return 'coverage'
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'coverage',
|
||||
}
|
||||
}
|
||||
|
||||
static render({ coverage }) {
|
||||
return {
|
||||
message: `${coverage.toFixed(0)}%`,
|
||||
color: scale(coverage),
|
||||
}
|
||||
}
|
||||
|
||||
transform({ json, branch }) {
|
||||
const { value: rawCoverage } = this.transformBranchInfoMetricValue({
|
||||
json,
|
||||
branch,
|
||||
metric: 'scrutinizer.test_coverage',
|
||||
})
|
||||
|
||||
if (!rawCoverage) {
|
||||
throw new NotFound({ prettyMessage: 'coverage not found' })
|
||||
}
|
||||
|
||||
return { coverage: rawCoverage * 100 }
|
||||
}
|
||||
|
||||
async makeBadge({ vcs, slug, branch }) {
|
||||
const json = await this.fetch({ schema, vcs, slug })
|
||||
const { coverage } = this.transform({ json, branch })
|
||||
return this.constructor.render({ coverage })
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerCoverage extends ScrutinizerCoverageBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/coverage',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer coverage (GitHub/BitBucket)',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
vcs: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: this.render({ coverage: 86 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ vcs, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs,
|
||||
slug: `${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerCoverageGitLab extends ScrutinizerCoverageBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/coverage/gl',
|
||||
pattern: ':instance/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
// There are no known anonymous accessible Scrutinizer reports available for GitLab repos.
|
||||
// The example used is valid, but the project will not be accessible if Shields users try to use it.
|
||||
// https://gitlab.propertywindow.nl/propertywindow/client
|
||||
// https://scrutinizer-ci.com/gl/propertywindow/propertywindow/client/badges/quality-score.png?b=master&s=dfae6992a48184cc2333b4c349cec0447f0d67c2
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer coverage (GitLab)',
|
||||
pattern: ':instance/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
instance: 'propertywindow',
|
||||
user: 'propertywindow',
|
||||
repo: 'client',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: this.render({ coverage: 94 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ instance, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs: 'gl',
|
||||
slug: `${instance}/${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerCoveragePlainGit extends ScrutinizerCoverageBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/coverage/gp',
|
||||
pattern: ':slug/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ slug, branch }) {
|
||||
return this.makeBadge({ vcs: 'gp', slug, branch })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
ScrutinizerCoverage,
|
||||
ScrutinizerCoverageGitLab,
|
||||
ScrutinizerCoveragePlainGit,
|
||||
]
|
||||
54
services/scrutinizer/scrutinizer-coverage.spec.js
Normal file
54
services/scrutinizer/scrutinizer-coverage.spec.js
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { test, given } = require('sazerac')
|
||||
const { NotFound } = require('..')
|
||||
const [ScrutinizerCoverage] = require('./scrutinizer-coverage.service')
|
||||
|
||||
describe('ScrutinizerCoverage', function() {
|
||||
test(ScrutinizerCoverage.render, () => {
|
||||
given({ coverage: 39 }).expect({
|
||||
message: '39%',
|
||||
color: 'red',
|
||||
})
|
||||
given({ coverage: 40 }).expect({
|
||||
message: '40%',
|
||||
color: 'yellow',
|
||||
})
|
||||
given({ coverage: 60 }).expect({
|
||||
message: '60%',
|
||||
color: 'yellow',
|
||||
})
|
||||
given({ coverage: 61 }).expect({
|
||||
message: '61%',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
})
|
||||
|
||||
context('transform()', function() {
|
||||
it('throws NotFound error when there is no coverage data', function() {
|
||||
try {
|
||||
ScrutinizerCoverage.prototype.transform({
|
||||
branch: 'master',
|
||||
json: {
|
||||
applications: {
|
||||
master: {
|
||||
index: {
|
||||
_embedded: {
|
||||
project: {
|
||||
metric_values: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
expect.fail('Expected to throw')
|
||||
} catch (e) {
|
||||
expect(e).to.be.an.instanceof(NotFound)
|
||||
expect(e.prettyMessage).to.equal('coverage not found')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
44
services/scrutinizer/scrutinizer-coverage.tester.js
Normal file
44
services/scrutinizer/scrutinizer-coverage.tester.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
const { isIntegerPercentage } = require('../test-validators')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'ScrutinizerCoverage',
|
||||
title: 'ScrutinizerCoverage',
|
||||
pathPrefix: '/scrutinizer/coverage',
|
||||
}))
|
||||
|
||||
t.create('code coverage (GitHub)')
|
||||
.get('/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('code coverage branch (GitHub)')
|
||||
.get('/g/PHPMailer/PHPMailer/master.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('code coverage (Bitbucket)')
|
||||
.get('/b/atlassian/python-bitbucket.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('code coverage private project')
|
||||
.get('/gl/propertywindow/propertywindow/client.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: 'not authorized to access project',
|
||||
})
|
||||
|
||||
t.create('code coverage nonexistent project')
|
||||
.get('/gp/foo.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: 'project not found',
|
||||
})
|
||||
148
services/scrutinizer/scrutinizer-quality.service.js
Normal file
148
services/scrutinizer/scrutinizer-quality.service.js
Normal file
@@ -0,0 +1,148 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { colorScale } = require('../color-formatters')
|
||||
const ScrutinizerBase = require('./scrutinizer-base')
|
||||
|
||||
const schema = Joi.object({
|
||||
default_branch: Joi.string().required(),
|
||||
applications: Joi.object()
|
||||
.pattern(
|
||||
/^/,
|
||||
Joi.object({
|
||||
index: Joi.object({
|
||||
_embedded: Joi.object({
|
||||
project: Joi.object({
|
||||
metric_values: Joi.object({
|
||||
'scrutinizer.quality': Joi.number().positive(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const scale = colorScale(
|
||||
[4, 5, 7, 9],
|
||||
['red', 'orange', 'yellow', 'green', 'brightgreen']
|
||||
)
|
||||
|
||||
class ScrutinizerQualityBase extends ScrutinizerBase {
|
||||
static get category() {
|
||||
return 'analysis'
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'code quality',
|
||||
}
|
||||
}
|
||||
|
||||
static render({ score }) {
|
||||
return {
|
||||
message: `${Math.round(score * 100) / 100}`,
|
||||
color: scale(score),
|
||||
}
|
||||
}
|
||||
|
||||
async makeBadge({ vcs, slug, branch }) {
|
||||
const json = await this.fetch({ schema, vcs, slug })
|
||||
const { value: score } = this.transformBranchInfoMetricValue({
|
||||
json,
|
||||
branch,
|
||||
metric: 'scrutinizer.quality',
|
||||
})
|
||||
return this.constructor.render({ score })
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerQuality extends ScrutinizerQualityBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/quality',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer code quality (GitHub/Bitbucket)',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
vcs: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: this.render({ score: 8.26 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ vcs, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs,
|
||||
slug: `${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerQualityGitLab extends ScrutinizerQualityBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/quality/gl',
|
||||
pattern: ':instance/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
// There are no known anonymous accessible Scrutinizer reports available for GitLab repos.
|
||||
// The example used is valid, but the project will not be accessible if Shields users try to use it.
|
||||
// https://gitlab.propertywindow.nl/propertywindow/client
|
||||
// https://scrutinizer-ci.com/gl/propertywindow/propertywindow/client/badges/quality-score.png?b=master&s=dfae6992a48184cc2333b4c349cec0447f0d67c2
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer coverage (GitLab)',
|
||||
pattern: ':instance/:user/:repo/:branch?',
|
||||
namedParams: {
|
||||
instance: 'propertywindow',
|
||||
user: 'propertywindow',
|
||||
repo: 'client',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: this.render({ score: 10.0 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ instance, user, repo, branch }) {
|
||||
return this.makeBadge({
|
||||
vcs: 'gl',
|
||||
slug: `${instance}/${user}/${repo}`,
|
||||
branch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ScrutinizerQualityPlainGit extends ScrutinizerQualityBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/quality/gp',
|
||||
pattern: ':slug/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ slug, branch }) {
|
||||
return this.makeBadge({ vcs: 'gp', slug, branch })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
ScrutinizerQuality,
|
||||
ScrutinizerQualityGitLab,
|
||||
ScrutinizerQualityPlainGit,
|
||||
]
|
||||
46
services/scrutinizer/scrutinizer-quality.tester.js
Normal file
46
services/scrutinizer/scrutinizer-quality.tester.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'ScrutinizerQuality',
|
||||
title: 'ScrutinizerQuality',
|
||||
pathPrefix: '/scrutinizer/quality',
|
||||
}))
|
||||
|
||||
const isQualityNumber = Joi.number().positive()
|
||||
|
||||
t.create('code quality (GitHub)')
|
||||
.get('/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: isQualityNumber,
|
||||
})
|
||||
|
||||
t.create('code quality branch (GitHub)')
|
||||
.get('/g/PHPMailer/PHPMailer/master.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: isQualityNumber,
|
||||
})
|
||||
|
||||
t.create('code quality (Bitbucket)')
|
||||
.get('/b/atlassian/python-bitbucket.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: isQualityNumber,
|
||||
})
|
||||
|
||||
t.create('code quality private project')
|
||||
.get('/gl/propertywindow/propertywindow/client.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: 'not authorized to access project',
|
||||
})
|
||||
|
||||
t.create('code quality nonexistent project')
|
||||
.get('/gp/foo.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: 'project not found',
|
||||
})
|
||||
42
services/scrutinizer/scrutinizer-redirect.service.js
Normal file
42
services/scrutinizer/scrutinizer-redirect.service.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const { redirector } = require('..')
|
||||
|
||||
const commonAttrs = {
|
||||
category: 'analysis',
|
||||
dateAdded: new Date('2019-04-24'),
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
redirector({
|
||||
route: {
|
||||
base: 'scrutinizer',
|
||||
pattern: ':vcs(g|b)/:user/:repo/:branch*',
|
||||
},
|
||||
transformPath: ({ vcs, user, repo, branch }) =>
|
||||
`/scrutinizer/quality/${vcs}/${user}/${repo}${
|
||||
branch ? `/${branch}` : ''
|
||||
}`,
|
||||
...commonAttrs,
|
||||
}),
|
||||
redirector({
|
||||
route: {
|
||||
base: 'scrutinizer/gl',
|
||||
pattern: ':instance/:user/:repo/:branch*',
|
||||
},
|
||||
transformPath: ({ instance, user, repo, branch }) =>
|
||||
`/scrutinizer/quality/gl/${instance}/${user}/${repo}${
|
||||
branch ? `/${branch}` : ''
|
||||
}`,
|
||||
...commonAttrs,
|
||||
}),
|
||||
redirector({
|
||||
route: {
|
||||
base: 'scrutinizer/gp',
|
||||
pattern: ':slug/:branch*',
|
||||
},
|
||||
transformPath: ({ slug, branch }) =>
|
||||
`/scrutinizer/quality/gp/${slug}${branch ? `/${branch}` : ''}`,
|
||||
...commonAttrs,
|
||||
}),
|
||||
]
|
||||
71
services/scrutinizer/scrutinizer-redirect.tester.js
Normal file
71
services/scrutinizer/scrutinizer-redirect.tester.js
Normal file
@@ -0,0 +1,71 @@
|
||||
'use strict'
|
||||
|
||||
const { ServiceTester } = require('../tester')
|
||||
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'ScrutinizerQualityRedirect',
|
||||
title: 'ScrutinizerQualityRedirect',
|
||||
pathPrefix: '/scrutinizer',
|
||||
}))
|
||||
|
||||
t.create('scrutinizer quality GitHub')
|
||||
.get('/g/doctrine/orm.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/g/doctrine/orm.svg')
|
||||
|
||||
t.create('scrutinizer quality GitHub (branch)')
|
||||
.get('/g/doctrine/orm/develop.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/g/doctrine/orm/develop.svg')
|
||||
|
||||
t.create('scrutinizer quality Bitbucket')
|
||||
.get('/b/doctrine/orm.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/b/doctrine/orm.svg')
|
||||
|
||||
t.create('scrutinizer quality Bitbucket (branch)')
|
||||
.get('/b/atlassian/python-bitbucket/develop.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader(
|
||||
'Location',
|
||||
'/scrutinizer/quality/b/atlassian/python-bitbucket/develop.svg'
|
||||
)
|
||||
|
||||
t.create('scrutinizer quality GitLab')
|
||||
.get('/gl/gitlab-com/foo/bar.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/gl/gitlab-com/foo/bar.svg')
|
||||
|
||||
t.create('scrutinizer quality GitLab (branch)')
|
||||
.get('/gl/gitlab-com/foo/bar/develop.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader(
|
||||
'Location',
|
||||
'/scrutinizer/quality/gl/gitlab-com/foo/bar/develop.svg'
|
||||
)
|
||||
|
||||
t.create('scrutinizer quality Plain Git')
|
||||
.get('/gp/bar.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/gp/bar.svg')
|
||||
|
||||
t.create('scrutinizer quality Plain Git (branch)')
|
||||
.get('/gp/bar/develop.svg', {
|
||||
followRedirect: false,
|
||||
})
|
||||
.expectStatus(301)
|
||||
.expectHeader('Location', '/scrutinizer/quality/gp/bar/develop.svg')
|
||||
@@ -1,208 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const { checkErrorResponse } = require('../../lib/error-helper')
|
||||
const {
|
||||
coveragePercentage: coveragePercentageColor,
|
||||
} = require('../color-formatters')
|
||||
|
||||
class ScrutinizerBuild extends LegacyService {
|
||||
static get category() {
|
||||
return 'build'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/build',
|
||||
pattern: ':vcsType/:user/:repo',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer build',
|
||||
namedParams: {
|
||||
vcsType: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'build',
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {}
|
||||
}
|
||||
|
||||
class ScrutinizerCoverage extends LegacyService {
|
||||
static get category() {
|
||||
return 'coverage'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer/coverage',
|
||||
pattern: ':vcsType/:user/:repo/:branch*',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer coverage',
|
||||
pattern: ':vcsType/:user/:repo',
|
||||
namedParams: {
|
||||
vcsType: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'coverage',
|
||||
message: '56%',
|
||||
color: 'yellow',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Scrutinizer coverage (branch)',
|
||||
pattern: ':vcsType/:user/:repo/:branch',
|
||||
namedParams: {
|
||||
vcsType: 'g',
|
||||
user: 'doctrine',
|
||||
repo: 'orm',
|
||||
branch: 'master',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'coverage',
|
||||
message: '73%',
|
||||
color: 'yellow',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {}
|
||||
}
|
||||
|
||||
// This legacy service should be rewritten to use e.g. BaseJsonService.
|
||||
//
|
||||
// Tips for rewriting:
|
||||
// https://github.com/badges/shields/blob/master/doc/rewriting-services.md
|
||||
//
|
||||
// Do not base new services on this code.
|
||||
class Scrutinizer extends LegacyService {
|
||||
static get category() {
|
||||
return 'analysis'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'scrutinizer',
|
||||
pattern: ':vcsType/:user/:repo',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Scrutinizer code quality',
|
||||
namedParams: {
|
||||
vcsType: 'g',
|
||||
user: 'filp',
|
||||
repo: 'whoops',
|
||||
},
|
||||
staticPreview: {
|
||||
label: 'code quality',
|
||||
message: '8.26',
|
||||
color: 'green',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
camp.route(
|
||||
/^\/scrutinizer(?:\/(build|coverage))?\/([^/]+\/[^/]+\/[^/]+|gp\/[^/])(?:\/(.+))?\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const type = match[1] ? match[1] : 'code quality'
|
||||
const repo = match[2] // eg, g/phpmyadmin/phpmyadmin
|
||||
let branch = match[3]
|
||||
const format = match[4]
|
||||
const apiUrl = `https://scrutinizer-ci.com/api/repositories/${repo}`
|
||||
const badgeData = getBadgeData(type, data)
|
||||
request(apiUrl, {}, (err, res, buffer) => {
|
||||
if (
|
||||
checkErrorResponse(badgeData, err, res, {
|
||||
404: 'project or branch not found',
|
||||
})
|
||||
) {
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const parsedData = JSON.parse(buffer)
|
||||
// Which branch are we dealing with?
|
||||
if (branch === undefined) {
|
||||
branch = parsedData.default_branch
|
||||
}
|
||||
if (type === 'coverage') {
|
||||
const percentage =
|
||||
parsedData.applications[branch].index._embedded.project
|
||||
.metric_values['scrutinizer.test_coverage'] * 100
|
||||
if (isNaN(percentage)) {
|
||||
badgeData.text[1] = 'unknown'
|
||||
badgeData.colorscheme = 'gray'
|
||||
} else {
|
||||
badgeData.text[1] = `${percentage.toFixed(0)}%`
|
||||
badgeData.colorscheme = coveragePercentageColor(percentage)
|
||||
}
|
||||
} else if (type === 'build') {
|
||||
const status = parsedData.applications[branch].build_status.status
|
||||
badgeData.text[1] = status
|
||||
if (status === 'passed') {
|
||||
badgeData.colorscheme = 'brightgreen'
|
||||
badgeData.text[1] = 'passing'
|
||||
} else if (status === 'failed' || status === 'error') {
|
||||
badgeData.colorscheme = 'red'
|
||||
} else if (status === 'pending') {
|
||||
badgeData.colorscheme = 'orange'
|
||||
}
|
||||
} else {
|
||||
let score =
|
||||
parsedData.applications[branch].index._embedded.project
|
||||
.metric_values['scrutinizer.quality']
|
||||
score = Math.round(score * 100) / 100
|
||||
badgeData.text[1] = score
|
||||
if (score > 9) {
|
||||
badgeData.colorscheme = 'brightgreen'
|
||||
} else if (score > 7) {
|
||||
badgeData.colorscheme = 'green'
|
||||
} else if (score > 5) {
|
||||
badgeData.colorscheme = 'yellow'
|
||||
} else if (score > 4) {
|
||||
badgeData.colorscheme = 'orange'
|
||||
} else {
|
||||
badgeData.colorscheme = 'red'
|
||||
}
|
||||
}
|
||||
sendBadge(format, badgeData)
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ScrutinizerBuild,
|
||||
ScrutinizerCoverage,
|
||||
Scrutinizer,
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isBuildStatus } = require('../build-status')
|
||||
const { ServiceTester } = require('../tester')
|
||||
const { isIntegerPercentage } = require('../test-validators')
|
||||
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'scrutinizer',
|
||||
title: 'Scrutinizer',
|
||||
}))
|
||||
|
||||
t.create('code quality')
|
||||
.get('/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: Joi.number().positive(),
|
||||
})
|
||||
|
||||
t.create('code quality (branch)')
|
||||
.get('/g/phpmyadmin/phpmyadmin/master.json')
|
||||
.expectBadge({
|
||||
label: 'code quality',
|
||||
message: Joi.number().positive(),
|
||||
})
|
||||
|
||||
t.create('code coverage')
|
||||
.get('/coverage/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('code coverage (branch)')
|
||||
.get('/coverage/g/PHPMailer/PHPMailer/master.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: isIntegerPercentage,
|
||||
})
|
||||
|
||||
t.create('build')
|
||||
.get('/build/g/filp/whoops.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
})
|
||||
|
||||
t.create('build (branch)')
|
||||
.get('/build/g/phpmyadmin/phpmyadmin/master.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: Joi.alternatives().try(isBuildStatus, Joi.equal('unknown')),
|
||||
})
|
||||
|
||||
t.create('project not found')
|
||||
.get('/build/g/does-not-exist/does-not-exist.json')
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'project or branch not found',
|
||||
})
|
||||
|
||||
t.create('code coverage unknown')
|
||||
.get('/coverage/g/phpmyadmin/phpmyadmin/master.json')
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: 'unknown',
|
||||
})
|
||||
|
||||
t.create('unexpected response data')
|
||||
.get('/coverage/g/filp/whoops.json')
|
||||
.intercept(nock =>
|
||||
nock('https://scrutinizer-ci.com')
|
||||
.get('/api/repositories/g/filp/whoops')
|
||||
.reply(200, '{"unexpected":"data"}')
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'coverage',
|
||||
message: 'invalid',
|
||||
})
|
||||
|
||||
t.create('build - unknown')
|
||||
.get('/build/g/filp/whoops.json')
|
||||
.intercept(nock =>
|
||||
nock('https://scrutinizer-ci.com')
|
||||
.get('/api/repositories/g/filp/whoops')
|
||||
.reply(200, {
|
||||
default_branch: 'master',
|
||||
applications: {
|
||||
master: {
|
||||
build_status: {
|
||||
status: 'unknown',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'build',
|
||||
message: 'unknown',
|
||||
color: 'lightgrey',
|
||||
})
|
||||
Reference in New Issue
Block a user