From 55ce947a35a0b32075fcdf7836785fb8b180c7d1 Mon Sep 17 00:00:00 2001 From: Pierre-Yves B Date: Tue, 15 Jan 2019 20:27:18 +0000 Subject: [PATCH] Extended usage of build-status.js tomore services (#2763) * Extended usage of build-status.js * Removed remaining status arguments --- doc/TUTORIAL.md | 1 + lib/build-status.js | 26 +++++++++++---- lib/build-status.spec.js | 14 ++++++-- services/appveyor/appveyor-base.js | 3 +- services/appveyor/appveyor-ci.service.js | 14 ++------ .../azure-devops-build.service.js | 9 ++--- services/azure-devops/azure-devops-helpers.js | 33 +++---------------- .../azure-devops-release.service.js | 7 ++-- .../bitbucket/bitbucket-pipelines.service.js | 22 +++++-------- services/bitbucket/bitbucket.tester.js | 4 +-- services/circleci/circleci.service.js | 16 ++++----- services/gitlab/gitlab-helpers.js | 16 --------- .../gitlab/gitlab-pipeline-status.service.js | 21 ++++-------- .../gitlab/gitlab-pipeline-status.tester.js | 8 ++--- services/readthedocs/readthedocs.service.js | 23 +++++-------- services/shippable/shippable.service.js | 27 +++++++-------- services/wercker/wercker.service.js | 31 ++++++++--------- services/wercker/wercker.tester.js | 2 +- 18 files changed, 113 insertions(+), 164 deletions(-) delete mode 100644 services/gitlab/gitlab-helpers.js diff --git a/doc/TUTORIAL.md b/doc/TUTORIAL.md index b3894e8db4..d52569bcce 100644 --- a/doc/TUTORIAL.md +++ b/doc/TUTORIAL.md @@ -214,6 +214,7 @@ Description of the code: 1. As with the first example, we declare strict mode at the start of each file. 2. Our badge will query a JSON API so we will extend `BaseJsonService` instead of `BaseService`. This contains some helpers to reduce the need for boilerplate when calling a JSON API. 3. In this case we are making a version badge, which is a common pattern. Instead of directly returning an object in this badge we will use a helper function to format our data consistently. There are a variety of helper functions to help with common tasks in `/lib`. Some useful generic helpers can be found in: + * [build-status.js](https://github.com/badges/shields/blob/master/lib/build-status.js) * [color-formatters.js](https://github.com/badges/shields/blob/master/lib/color-formatters.js) * [licenses.js](https://github.com/badges/shields/blob/master/lib/licenses.js) * [text-formatters.js](https://github.com/badges/shields/blob/master/lib/text-formatters.js) diff --git a/lib/build-status.js b/lib/build-status.js index b1d68e54ff..523686e540 100644 --- a/lib/build-status.js +++ b/lib/build-status.js @@ -2,12 +2,22 @@ const Joi = require('joi') -const happyStatuses = ['passed', 'passing', 'success'] +const greenStatuses = [ + 'fixed', + 'passed', + 'passing', + 'succeeded', + 'success', + 'successful', +] -const unhappyStatuses = ['error', 'failed', 'failing', 'unstable'] +const orangeStatuses = ['partially succeeded', 'unstable', 'timeout'] + +const redStatuses = ['error', 'failed', 'failing'] const otherStatuses = [ 'building', + 'canceled', 'cancelled', 'expired', 'no tests', @@ -20,21 +30,25 @@ const otherStatuses = [ 'scheduled', 'skipped', 'stopped', - 'timeout', 'waiting', ] const isBuildStatus = Joi.equal( - happyStatuses.concat(unhappyStatuses).concat(otherStatuses) + greenStatuses + .concat(orangeStatuses) + .concat(redStatuses) + .concat(otherStatuses) ) function renderBuildStatusBadge({ label, status }) { let message let color - if (happyStatuses.includes(status)) { + if (greenStatuses.includes(status)) { message = 'passing' color = 'brightgreen' - } else if (unhappyStatuses.includes(status)) { + } else if (orangeStatuses.includes(status)) { + color = 'orange' + } else if (redStatuses.includes(status)) { message = status === 'failed' ? 'failing' : status color = 'red' } else { diff --git a/lib/build-status.spec.js b/lib/build-status.spec.js index d26be11b15..72c5cd45ae 100644 --- a/lib/build-status.spec.js +++ b/lib/build-status.spec.js @@ -29,26 +29,37 @@ test(renderBuildStatusBadge, () => { test(renderBuildStatusBadge, () => { forCases([ + given({ status: 'fixed' }), given({ status: 'passed' }), given({ status: 'passing' }), + given({ status: 'succeeded' }), given({ status: 'success' }), + given({ status: 'successful' }), ]).assert('should be brightgreen', b => expect(b).to.include({ color: 'brightgreen' }) ) }) +test(renderBuildStatusBadge, () => { + forCases([ + given({ status: 'partially succeeded' }), + given({ status: 'timeout' }), + given({ status: 'unstable' }), + ]).assert('should be orange', b => expect(b).to.include({ color: 'orange' })) +}) + test(renderBuildStatusBadge, () => { forCases([ given({ status: 'error' }), given({ status: 'failed' }), given({ status: 'failing' }), - given({ status: 'unstable' }), ]).assert('should be red', b => expect(b).to.include({ color: 'red' })) }) test(renderBuildStatusBadge, () => { forCases([ given({ status: 'building' }), + given({ status: 'canceled' }), given({ status: 'cancelled' }), given({ status: 'expired' }), given({ status: 'no tests' }), @@ -61,7 +72,6 @@ test(renderBuildStatusBadge, () => { given({ status: 'scheduled' }), given({ status: 'skipped' }), given({ status: 'stopped' }), - given({ status: 'timeout' }), given({ status: 'waiting' }), ]).assert('should have undefined color', b => expect(b).to.include({ color: undefined }) diff --git a/services/appveyor/appveyor-base.js b/services/appveyor/appveyor-base.js index e7a82a628e..c3e773a558 100644 --- a/services/appveyor/appveyor-base.js +++ b/services/appveyor/appveyor-base.js @@ -3,10 +3,11 @@ const Joi = require('joi') const BaseJsonService = require('../base-json') const { nonNegativeInteger } = require('../validators') +const { isBuildStatus } = require('../../lib/build-status') const schema = Joi.object({ build: Joi.object({ - status: Joi.string().required(), + status: isBuildStatus, jobs: Joi.array() .items({ testsCount: nonNegativeInteger, diff --git a/services/appveyor/appveyor-ci.service.js b/services/appveyor/appveyor-ci.service.js index b712a8da73..0fe8bf2193 100644 --- a/services/appveyor/appveyor-ci.service.js +++ b/services/appveyor/appveyor-ci.service.js @@ -2,6 +2,8 @@ const AppVeyorBase = require('./appveyor-base') +const { renderBuildStatusBadge } = require('../../lib/build-status') + module.exports = class AppVeyorCi extends AppVeyorBase { static get route() { return this.buildRoute('appveyor/ci') @@ -25,17 +27,7 @@ module.exports = class AppVeyorCi extends AppVeyorBase { } static render({ status }) { - if (status === 'success') { - return { message: 'passing', color: 'brightgreen' } - } else if ( - status !== 'running' && - status !== 'queued' && - status !== 'no builds found' - ) { - return { message: 'failing', color: 'red' } - } else { - return { message: status } - } + return renderBuildStatusBadge({ status }) } async handle({ user, repo, branch }) { diff --git a/services/azure-devops/azure-devops-build.service.js b/services/azure-devops/azure-devops-build.service.js index 1888bd3d5a..80f6f420dd 100644 --- a/services/azure-devops/azure-devops-build.service.js +++ b/services/azure-devops/azure-devops-build.service.js @@ -2,7 +2,8 @@ const BaseSvgService = require('../base-svg-scraping') const { NotFound } = require('../errors') -const { keywords, fetch, render } = require('./azure-devops-helpers') +const { keywords, fetch } = require('./azure-devops-helpers') +const { renderBuildStatusBadge } = require('../../lib/build-status') const documentation = `

@@ -50,7 +51,7 @@ module.exports = class AzureDevOpsBuild extends BaseSvgService { projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96', definitionId: '2', }, - staticPreview: render({ status: 'succeeded' }), + staticPreview: renderBuildStatusBadge({ status: 'succeeded' }), keywords, documentation, }, @@ -64,7 +65,7 @@ module.exports = class AzureDevOpsBuild extends BaseSvgService { definitionId: '2', branch: 'master', }, - staticPreview: render({ status: 'succeeded' }), + staticPreview: renderBuildStatusBadge({ status: 'succeeded' }), keywords, documentation, }, @@ -86,6 +87,6 @@ module.exports = class AzureDevOpsBuild extends BaseSvgService { if (status === 'unknown') { throw new NotFound({ prettyMessage: 'project not found' }) } - return render({ status }) + return renderBuildStatusBadge({ status }) } } diff --git a/services/azure-devops/azure-devops-helpers.js b/services/azure-devops/azure-devops-helpers.js index 80d4fabb81..15b80926ce 100644 --- a/services/azure-devops/azure-devops-helpers.js +++ b/services/azure-devops/azure-devops-helpers.js @@ -2,17 +2,14 @@ const Joi = require('joi') const serverSecrets = require('../../lib/server-secrets') +const { isBuildStatus } = require('../../lib/build-status') const keywords = ['vso', 'vsts', 'azure-devops'] const schema = Joi.object({ - message: Joi.equal( - 'succeeded', - 'partially suceeded', - 'failed', - 'unknown', - 'set up now' - ).required(), + message: Joi.alternatives() + .try(isBuildStatus, Joi.equal('unknown'), Joi.equal('set up now')) + .required(), }).required() async function fetch(serviceInstance, { url, qs = {}, errorMessages }) { @@ -26,26 +23,6 @@ async function fetch(serviceInstance, { url, qs = {}, errorMessages }) { return { status } } -function render({ status }) { - switch (status) { - case 'succeeded': - return { - message: 'passing', - color: 'brightgreen', - } - case 'partially succeeded': - return { - message: 'passing', - color: 'orange', - } - case 'failed': - return { - message: 'failing', - color: 'red', - } - } -} - function getHeaders() { const headers = {} if (serverSecrets.azure_devops_token) { @@ -57,4 +34,4 @@ function getHeaders() { return headers } -module.exports = { keywords, fetch, render, getHeaders } +module.exports = { keywords, fetch, getHeaders } diff --git a/services/azure-devops/azure-devops-release.service.js b/services/azure-devops/azure-devops-release.service.js index dc3f8926d3..94ff6ab7c1 100644 --- a/services/azure-devops/azure-devops-release.service.js +++ b/services/azure-devops/azure-devops-release.service.js @@ -1,7 +1,8 @@ 'use strict' const BaseSvgService = require('../base-svg-scraping') -const { keywords, fetch, render } = require('./azure-devops-helpers') +const { keywords, fetch } = require('./azure-devops-helpers') +const { renderBuildStatusBadge } = require('../../lib/build-status') const documentation = `

@@ -46,7 +47,7 @@ module.exports = class AzureDevOpsRelease extends BaseSvgService { definitionId: '1', environmentId: '1', }, - staticPreview: render({ status: 'succeeded' }), + staticPreview: renderBuildStatusBadge({ status: 'succeeded' }), keywords, documentation, }, @@ -69,6 +70,6 @@ module.exports = class AzureDevOpsRelease extends BaseSvgService { 500: 'inaccessible or definition not found', }, }) - return render(props) + return renderBuildStatusBadge(props) } } diff --git a/services/bitbucket/bitbucket-pipelines.service.js b/services/bitbucket/bitbucket-pipelines.service.js index e3ab4265de..d1253d4581 100644 --- a/services/bitbucket/bitbucket-pipelines.service.js +++ b/services/bitbucket/bitbucket-pipelines.service.js @@ -2,6 +2,7 @@ const Joi = require('joi') const BaseJsonService = require('../base-json') +const { renderBuildStatusBadge } = require('../../lib/build-status') const bitbucketPipelinesSchema = Joi.object({ values: Joi.array() @@ -10,7 +11,13 @@ const bitbucketPipelinesSchema = Joi.object({ state: Joi.object({ name: Joi.string().required(), result: Joi.object({ - name: Joi.string().required(), + name: Joi.equal( + 'SUCCESSFUL', + 'FAILED', + 'ERROR', + 'STOPPED', + 'EXPIRED' + ), }).required(), }).required(), }) @@ -39,18 +46,7 @@ module.exports = class BitbucketPipelines extends BaseJsonService { } static render({ status }) { - const responses = { - SUCCESSFUL: { message: 'passing', color: 'brightgreen' }, - FAILED: { message: 'failing', color: 'red' }, - ERROR: { message: 'error', color: 'red' }, - STOPPED: { message: 'stopped', color: 'yellow' }, - EXPIRED: { message: 'expired', color: 'yellow' }, - 'never built': { message: 'never built', color: 'lightgrey' }, - } - if (Object.keys(responses).includes(status)) { - return responses[status] - } - return { message: 'unknown', color: 'lightgrey' } + return renderBuildStatusBadge({ status: status.toLowerCase() }) } static transform(data) { diff --git a/services/bitbucket/bitbucket.tester.js b/services/bitbucket/bitbucket.tester.js index a2d64de849..75601ef8d3 100644 --- a/services/bitbucket/bitbucket.tester.js +++ b/services/bitbucket/bitbucket.tester.js @@ -298,11 +298,11 @@ t.create('build result (expired)') ) .expectJSON({ name: 'build', value: 'expired' }) -t.create('build result (unknown)') +t.create('build result (unexpected status)') .get('/pipelines/atlassian/adf-builder-javascript.json') .intercept(nock => nock('https://api.bitbucket.org') .get(/^\/2.0\/.*/) .reply(200, bitbucketApiResponse('NEW_AND_UNEXPECTED')) ) - .expectJSON({ name: 'build', value: 'unknown' }) + .expectJSON({ name: 'build', value: 'invalid response data' }) diff --git a/services/circleci/circleci.service.js b/services/circleci/circleci.service.js index 9b3e66bfa9..8df0b1b3a5 100644 --- a/services/circleci/circleci.service.js +++ b/services/circleci/circleci.service.js @@ -2,9 +2,13 @@ const Joi = require('joi') const BaseJsonService = require('../base-json') +const { + isBuildStatus, + renderBuildStatusBadge, +} = require('../../lib/build-status') const circleSchema = Joi.array() - .items(Joi.object({ status: Joi.string().required() })) + .items(Joi.object({ status: isBuildStatus })) .min(1) .max(1) .required() @@ -38,15 +42,7 @@ module.exports = class CircleCi extends BaseJsonService { } static render({ status }) { - if (['success', 'fixed'].includes(status)) { - return { message: 'passing', color: 'brightgreen' } - } else if (status === 'failed') { - return { message: 'failed', color: 'red' } - } else if (['no_tests', 'scheduled', 'not_run'].includes(status)) { - return { message: status.replace('_', ' '), color: 'yellow' } - } else { - return { message: status.replace('_', ' '), color: 'lightgrey' } - } + return renderBuildStatusBadge({ status: status.replace('_', ' ') }) } async handle({ token, vcsType, userRepo, branch }) { diff --git a/services/gitlab/gitlab-helpers.js b/services/gitlab/gitlab-helpers.js deleted file mode 100644 index d92dc98386..0000000000 --- a/services/gitlab/gitlab-helpers.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' - -const Joi = require('joi') - -const isPipelineStatus = Joi.equal( - 'pending', - 'running', - 'passed', - 'failed', - 'skipped', - 'canceled' -).required() - -module.exports = { - isPipelineStatus, -} diff --git a/services/gitlab/gitlab-pipeline-status.service.js b/services/gitlab/gitlab-pipeline-status.service.js index f5866eb35e..5bf13ecff7 100644 --- a/services/gitlab/gitlab-pipeline-status.service.js +++ b/services/gitlab/gitlab-pipeline-status.service.js @@ -4,11 +4,14 @@ const Joi = require('joi') const BaseSvgScrapingService = require('../base-svg-scraping') const { optionalUrl } = require('../validators') const { NotFound } = require('../errors') -const { isPipelineStatus } = require('./gitlab-helpers') +const { + isBuildStatus, + renderBuildStatusBadge, +} = require('../../lib/build-status') const badgeSchema = Joi.object({ message: Joi.alternatives() - .try([isPipelineStatus, Joi.equal('unknown')]) + .try(isBuildStatus, Joi.equal('unknown')) .required(), }).required() @@ -58,19 +61,7 @@ module.exports = class GitlabPipelineStatus extends BaseSvgScrapingService { } static render({ status }) { - const color = { - pending: 'yellow', - running: 'yellow', - passed: 'brightgreen', - failed: 'red', - skipped: 'lightgray', - canceled: 'lightgray', - }[status] - - return { - message: status, - color, - } + return renderBuildStatusBadge({ status }) } async handle({ user, repo, branch = 'master' }, queryParams) { diff --git a/services/gitlab/gitlab-pipeline-status.tester.js b/services/gitlab/gitlab-pipeline-status.tester.js index 01e2a1a97e..01f0d0162e 100644 --- a/services/gitlab/gitlab-pipeline-status.tester.js +++ b/services/gitlab/gitlab-pipeline-status.tester.js @@ -1,7 +1,7 @@ 'use strict' const Joi = require('joi') -const { isPipelineStatus } = require('./gitlab-helpers') +const { isBuildStatus } = require('../../lib/build-status') const t = (module.exports = require('../create-service-tester')()) @@ -10,7 +10,7 @@ t.create('Pipeline status') .expectJSONTypes( Joi.object().keys({ name: 'build', - value: isPipelineStatus, + value: isBuildStatus, }) ) @@ -19,7 +19,7 @@ t.create('Pipeline status (branch)') .expectJSONTypes( Joi.object().keys({ name: 'build', - value: isPipelineStatus, + value: isBuildStatus, }) ) @@ -42,6 +42,6 @@ t.create('Pipeline status (custom gitlab URL)') .expectJSONTypes( Joi.object().keys({ name: 'build', - value: isPipelineStatus, + value: isBuildStatus, }) ) diff --git a/services/readthedocs/readthedocs.service.js b/services/readthedocs/readthedocs.service.js index 3952f16503..93391e1b85 100644 --- a/services/readthedocs/readthedocs.service.js +++ b/services/readthedocs/readthedocs.service.js @@ -3,11 +3,17 @@ const Joi = require('joi') const BaseSvgScrapingService = require('../base-svg-scraping') const { NotFound } = require('../errors') +const { + isBuildStatus, + renderBuildStatusBadge, +} = require('../../lib/build-status') const keywords = ['documentation'] const schema = Joi.object({ - message: Joi.string().required(), + message: Joi.alternatives() + .try(isBuildStatus, Joi.equal('unknown')) + .required(), }).required() module.exports = class ReadTheDocs extends BaseSvgScrapingService { @@ -48,20 +54,7 @@ module.exports = class ReadTheDocs extends BaseSvgScrapingService { } static render({ status }) { - let color - if (status === 'passing') { - color = 'brightgreen' - } else if (status === 'failing') { - color = 'red' - } else if (status === 'unknown') { - color = 'yellow' - } else { - color = 'red' - } - return { - message: status, - color, - } + return renderBuildStatusBadge({ status }) } async handle({ project, version }) { diff --git a/services/shippable/shippable.service.js b/services/shippable/shippable.service.js index fe77b9c315..12fc651c57 100644 --- a/services/shippable/shippable.service.js +++ b/services/shippable/shippable.service.js @@ -3,19 +3,20 @@ const BaseJsonService = require('../base-json') const Joi = require('joi') const { NotFound } = require('../errors') +const { renderBuildStatusBadge } = require('../../lib/build-status') // source: https://github.com/badges/shields/pull/1362#discussion_r161693830 const statusCodes = { - 0: { color: '#5183A0', label: 'waiting' }, - 10: { color: '#5183A0', label: 'queued' }, - 20: { color: '#5183A0', label: 'processing' }, - 30: { color: '#44CC11', label: 'success' }, - 40: { color: '#F8A97D', label: 'skipped' }, - 50: { color: '#CEA61B', label: 'unstable' }, - 60: { color: '#555555', label: 'timeout' }, - 70: { color: '#6BAFBD', label: 'cancelled' }, - 80: { color: '#DC5F59', label: 'failed' }, - 90: { color: '#555555', label: 'stopped' }, + 0: 'waiting', + 10: 'queued', + 20: 'processing', + 30: 'success', + 40: 'skipped', + 50: 'unstable', + 60: 'timeout', + 70: 'cancelled', + 80: 'failed', + 90: 'stopped', } const schema = Joi.array() @@ -71,11 +72,7 @@ module.exports = class Shippable extends BaseJsonService { } static render({ code }) { - return { - label: 'build', - message: statusCodes[code].label, - color: statusCodes[code].color, - } + return renderBuildStatusBadge({ label: 'build', status: statusCodes[code] }) } async handle({ projectId, branch = 'master' }) { diff --git a/services/wercker/wercker.service.js b/services/wercker/wercker.service.js index 2264042108..8d8e89d59e 100644 --- a/services/wercker/wercker.service.js +++ b/services/wercker/wercker.service.js @@ -2,12 +2,15 @@ const Joi = require('joi') const BaseJsonService = require('../base-json') +const { + isBuildStatus, + renderBuildStatusBadge, +} = require('../../lib/build-status') const werckerSchema = Joi.array() .items( Joi.object({ - status: Joi.string().required(), - result: Joi.string().required(), + result: isBuildStatus, }) ) .min(0) @@ -40,15 +43,8 @@ module.exports = class Wercker extends BaseJsonService { }) } - static render({ status, result }) { - if (status === 'finished') { - if (result === 'passed') { - return { message: 'passing', color: 'brightgreen' } - } else { - return { message: result, color: 'red' } - } - } - return { message: status } + static render({ result }) { + return renderBuildStatusBadge({ status: result }) } async handle({ projectId, applicationName, branch }) { @@ -61,12 +57,11 @@ module.exports = class Wercker extends BaseJsonService { }) if (json.length === 0) { return this.constructor.render({ - status: 'finished', result: 'no builds', }) } - const { status, result } = json[0] - return this.constructor.render({ status, result }) + const { result } = json[0] + return this.constructor.render({ result }) } // Metadata @@ -89,7 +84,7 @@ module.exports = class Wercker extends BaseJsonService { title: `Wercker CI Run`, pattern: 'ci/:applicationId', namedParams: { applicationId: '559e33c8e982fc615500b357' }, - staticPreview: this.render({ status: 'finished', result: 'passed' }), + staticPreview: this.render({ result: 'passed' }), }, { title: `Wercker CI Run`, @@ -98,7 +93,7 @@ module.exports = class Wercker extends BaseJsonService { applicationId: '559e33c8e982fc615500b357', branch: 'master', }, - staticPreview: this.render({ status: 'finished', result: 'passed' }), + staticPreview: this.render({ result: 'passed' }), }, { title: `Wercker Build`, @@ -107,7 +102,7 @@ module.exports = class Wercker extends BaseJsonService { userName: 'wercker', applicationName: 'go-wercker-api', }, - staticPreview: this.render({ status: 'finished', result: 'passed' }), + staticPreview: this.render({ result: 'passed' }), }, { title: `Wercker Build branch`, @@ -117,7 +112,7 @@ module.exports = class Wercker extends BaseJsonService { applicationName: 'go-wercker-api', branch: 'master', }, - staticPreview: this.render({ status: 'finished', result: 'passed' }), + staticPreview: this.render({ result: 'passed' }), }, ] } diff --git a/services/wercker/wercker.tester.js b/services/wercker/wercker.tester.js index 626ff32ef1..5ea546a45c 100644 --- a/services/wercker/wercker.tester.js +++ b/services/wercker/wercker.tester.js @@ -42,7 +42,7 @@ t.create('Build failed (mocked)') .get('/wercker/go-wercker-api/builds?limit=1') .reply(200, [{ status: 'finished', result: 'failed' }]) ) - .expectJSON({ name: 'build', value: 'failed', colorB: colorScheme.red }) + .expectJSON({ name: 'build', value: 'failing', colorB: colorScheme.red }) t.create('CI status by ID') .get('/ci/559e33c8e982fc615500b357.json')