From 1e6acc830ba7fca2afea1a64c37f1829af35b52a Mon Sep 17 00:00:00 2001 From: Hrishikesh Patil Date: Fri, 7 Aug 2020 05:45:47 +0530 Subject: [PATCH] [GitlabCoverage] Add badge for code coverage (#5262) * Add base for GitLab code coverage badge * Change examples to repos which have implemented code coverage reporting in GitLab UI * Add tests for Gitlab Coverage badge * Fix test data that was not changed * Update code according to new branch detection Signed-off-by: Hrishikesh Patil * Update docs and examples, make branch required option that was missed in previous commit Signed-off-by: Hrishikesh Patil * Apply suggestions from PR discussion Signed-off-by: Hrishikesh Patil * Remove default value of branch param Signed-off-by: Hrishikesh Patil * Apply further discussed changes to service Signed-off-by: Hrishikesh Patil * Fix tests that were failing Signed-off-by: Hrishikesh Patil * Add fetch and transform functions Signed-off-by: Hrishikesh Patil * Add examples and tests for custom job name Signed-off-by: Hrishikesh Patil * Add finishing touches Signed-off-by: Hrishikesh Patil * Change to a working example Signed-off-by: Hrishikesh Patil Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com> --- services/gitlab/gitlab-coverage.service.js | 149 +++++++++++++++++++++ services/gitlab/gitlab-coverage.tester.js | 64 +++++++++ 2 files changed, 213 insertions(+) create mode 100644 services/gitlab/gitlab-coverage.service.js create mode 100644 services/gitlab/gitlab-coverage.tester.js diff --git a/services/gitlab/gitlab-coverage.service.js b/services/gitlab/gitlab-coverage.service.js new file mode 100644 index 0000000000..7c8f94526b --- /dev/null +++ b/services/gitlab/gitlab-coverage.service.js @@ -0,0 +1,149 @@ +'use strict' + +const Joi = require('@hapi/joi') +const { coveragePercentage } = require('../color-formatters') +const { optionalUrl } = require('../validators') +const { BaseSvgScrapingService, NotFound } = require('..') + +const schema = Joi.object({ + message: Joi.string() + .regex(/^([0-9]+\.[0-9]+%)|unknown$/) + .required(), +}).required() + +const queryParamSchema = Joi.object({ + gitlab_url: optionalUrl, + job_name: Joi.string(), +}).required() + +const documentation = ` +

+ Important: If your project is publicly visible, but the badge is like this: + coverage not set up +

+

+ Check if your pipelines are publicly visible as well.
+ Navigate to your project settings on GitLab and choose General Pipelines under CI/CD.
+ Then tick the setting Public pipelines. +

+

+ Now your settings should look like this: +

+Setting Public pipelines set +

+Also make sure you have set up code covrage parsing as described here +

+

+ Your badge should be working fine now. +

+` + +module.exports = class GitlabCoverage extends BaseSvgScrapingService { + static get category() { + return 'coverage' + } + + static get route() { + return { + base: 'gitlab/coverage', + pattern: ':user/:repo/:branch', + queryParamSchema, + } + } + + static get examples() { + return [ + { + title: 'Gitlab code coverage', + namedParams: { + user: 'gitlab-org', + repo: 'gitlab-runner', + branch: 'master', + }, + staticPreview: this.render({ coverage: 67 }), + documentation, + }, + { + title: 'Gitlab code coverage (specific job)', + namedParams: { + user: 'gitlab-org', + repo: 'gitlab-runner', + branch: 'master', + }, + queryParams: { job_name: 'test coverage report' }, + staticPreview: this.render({ coverage: 96 }), + documentation, + }, + { + title: 'Gitlab code coverage (self-hosted)', + namedParams: { user: 'GNOME', repo: 'libhandy', branch: 'master' }, + queryParams: { gitlab_url: 'https://gitlab.gnome.org' }, + staticPreview: this.render({ coverage: 93 }), + documentation, + }, + { + title: 'Gitlab code coverage (self-hosted, specific job)', + namedParams: { user: 'GNOME', repo: 'libhandy', branch: 'master' }, + queryParams: { + gitlab_url: 'https://gitlab.gnome.org', + job_name: 'unit-test', + }, + staticPreview: this.render({ coverage: 93 }), + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { label: 'coverage' } + } + + static render({ coverage }) { + return { + message: `${coverage.toFixed(0)}%`, + color: coveragePercentage(coverage), + } + } + + async fetch({ + user, + repo, + branch, + gitlab_url: baseUrl = 'https://gitlab.com', + job_name, + }) { + // Since the URL doesn't return a usable value when an invalid job name is specified, + // it is recommended to not use the query param at all if not required + job_name = job_name ? `?job=${job_name}` : '' + const url = `${baseUrl}/${user}/${repo}/badges/${branch}/coverage.svg${job_name}` + const errorMessages = { + 401: 'repo not found', + 404: 'repo not found', + } + return this._requestSvg({ + schema, + url, + errorMessages, + }) + } + + static transform({ coverage }) { + if (coverage === 'unknown') { + throw new NotFound({ prettyMessage: 'not set up' }) + } + return Number(coverage.slice(0, -1)) + } + + async handle({ user, repo, branch }, { gitlab_url, job_name }) { + const { message: coverage } = await this.fetch({ + user, + repo, + branch, + gitlab_url, + job_name, + }) + return this.constructor.render({ + coverage: this.constructor.transform({ coverage }), + }) + } +} diff --git a/services/gitlab/gitlab-coverage.tester.js b/services/gitlab/gitlab-coverage.tester.js new file mode 100644 index 0000000000..e655e39211 --- /dev/null +++ b/services/gitlab/gitlab-coverage.tester.js @@ -0,0 +1,64 @@ +'use strict' + +const { isIntegerPercentage } = require('../test-validators') +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('Coverage (branch)') + .get('/gitlab-org/gitlab-runner/12-0-stable.json') + .expectBadge({ + label: 'coverage', + message: isIntegerPercentage, + }) + +t.create('Coverage (existent branch but coverage not set up)') + .get('/gitlab-org/gitlab-git-http-server/master.json') + .expectBadge({ + label: 'coverage', + message: 'not set up', + }) + +t.create('Coverage (nonexistent branch)') + .get('/gitlab-org/gitlab-runner/nope-not-a-branch.json') + .expectBadge({ + label: 'coverage', + message: 'not set up', + }) + +t.create('Coverage (nonexistent repo)') + .get('/this-repo/does-not-exist/neither-branch.json') + .expectBadge({ + label: 'coverage', + message: 'inaccessible', + }) + +t.create('Coverage (custom job)') + .get( + '/gitlab-org/gitlab-runner/12-0-stable.json?job_name=test coverage report' + ) + .expectBadge({ + label: 'coverage', + message: isIntegerPercentage, + }) + +t.create('Coverage (custom invalid job)') + .get('/gitlab-org/gitlab-runner/12-0-stable.json?job_name=i dont exist') + .expectBadge({ + label: 'coverage', + message: 'not set up', + }) + +t.create('Coverage (custom gitlab URL)') + .get('/GNOME/libhandy/master.json?gitlab_url=https://gitlab.gnome.org') + .expectBadge({ + label: 'coverage', + message: isIntegerPercentage, + }) + +t.create('Coverage (custom gitlab URL and job)') + .get( + '/GNOME/libhandy/master.json?gitlab_url=https://gitlab.gnome.org&job_name=unit-test' + ) + .expectBadge({ + label: 'coverage', + message: isIntegerPercentage, + })