From 792ecb7c01dedb21ea2a20661821fba35ca01ede Mon Sep 17 00:00:00 2001 From: Jacob Colvin Date: Mon, 9 Mar 2020 17:10:12 -0400 Subject: [PATCH] Add [github] Milestone service (#4747) * Add isMetricOverMetric test validator * Add Github Milestone and Milestone-Detail Co-authored-by: Pierre-Yves B --- .../github/github-milestone-detail.service.js | 110 ++++++++++++++++++ .../github/github-milestone-detail.tester.js | 55 +++++++++ services/github/github-milestone.service.js | 93 +++++++++++++++ services/github/github-milestone.tester.js | 35 ++++++ services/test-validators.js | 5 + 5 files changed, 298 insertions(+) create mode 100644 services/github/github-milestone-detail.service.js create mode 100644 services/github/github-milestone-detail.tester.js create mode 100644 services/github/github-milestone.service.js create mode 100644 services/github/github-milestone.tester.js diff --git a/services/github/github-milestone-detail.service.js b/services/github/github-milestone-detail.service.js new file mode 100644 index 0000000000..1cbbf6cd89 --- /dev/null +++ b/services/github/github-milestone-detail.service.js @@ -0,0 +1,110 @@ +'use strict' + +const Joi = require('@hapi/joi') +const { metric } = require('../text-formatters') +const { nonNegativeInteger } = require('../validators') +const { GithubAuthV3Service } = require('./github-auth-service') +const { documentation, errorMessagesFor } = require('./github-helpers') + +const schema = Joi.object({ + open_issues: nonNegativeInteger, + closed_issues: nonNegativeInteger, + title: Joi.string().required(), +}).required() + +module.exports = class GithubMilestoneDetail extends GithubAuthV3Service { + static get category() { + return 'issue-tracking' + } + + static get route() { + return { + base: 'github/milestones', + pattern: + ':variant(issues-closed|issues-open|issues-total|progress|progress-percent)/:user/:repo/:number([0-9]+)', + } + } + + static get examples() { + return [ + { + title: 'GitHub milestone', + namedParams: { + variant: 'issues-open', + user: 'badges', + repo: 'shields', + number: '1', + }, + staticPreview: { + label: 'milestone issues', + message: '17/22', + color: 'blue', + }, + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { + label: 'milestones', + color: 'informational', + } + } + + static render({ user, repo, variant, number, milestone }) { + let milestoneMetric + let color + let label = '' + + switch (variant) { + case 'issues-open': + milestoneMetric = milestone.open_issues + color = 'red' + label = 'open issues' + break + case 'issues-closed': + milestoneMetric = milestone.closed_issues + color = 'green' + label = 'closed issues' + break + case 'issues-total': + milestoneMetric = milestone.open_issues + milestone.closed_issues + color = 'blue' + label = 'issues' + break + case 'progress': + milestoneMetric = `${milestone.closed_issues}/${milestone.open_issues + + milestone.closed_issues}` + color = 'blue' + break + case 'progress-percent': + milestoneMetric = `${Math.floor( + (milestone.closed_issues / + (milestone.open_issues + milestone.closed_issues)) * + 100 + )}%` + color = 'blue' + } + + return { + label: `${milestone.title} ${label}`, + message: metric(milestoneMetric), + color, + link: [`https://github.com/${user}/${repo}/milestone/${number}`], + } + } + + async fetch({ user, repo, number }) { + return this._requestJson({ + url: `/repos/${user}/${repo}/milestones/${number}`, + schema, + errorMessages: errorMessagesFor(`repo or milestone not found`), + }) + } + + async handle({ user, repo, variant, number }) { + const milestone = await this.fetch({ user, repo, number }) + return this.constructor.render({ user, repo, variant, number, milestone }) + } +} diff --git a/services/github/github-milestone-detail.tester.js b/services/github/github-milestone-detail.tester.js new file mode 100644 index 0000000000..699c5afac4 --- /dev/null +++ b/services/github/github-milestone-detail.tester.js @@ -0,0 +1,55 @@ +'use strict' + +const { + isMetric, + isMetricOverMetric, + isIntegerPercentage, +} = require('../test-validators') +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('Milestone Open Issues') + .get('/issues-open/MacroPower/milestone-test/1.json') + .expectBadge({ + label: 'openWithOneOpenIssue open issues', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestone/1`], + }) + +t.create('Milestone Closed Issues') + .get('/issues-closed/MacroPower/milestone-test/3.json') + .expectBadge({ + label: 'closedWithOneClosedIssue closed issues', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestone/3`], + }) + +t.create('Milestone Total Issues') + .get('/issues-total/MacroPower/milestone-test/2.json') + .expectBadge({ + label: 'openWithOneOpenOneClosedIssue issues', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestone/2`], + }) + +t.create('Milestone Progress') + .get('/progress/MacroPower/milestone-test/2.json') + .expectBadge({ + label: 'openWithOneOpenOneClosedIssue', + message: isMetricOverMetric, + link: [`https://github.com/MacroPower/milestone-test/milestone/2`], + }) + +t.create('Milestone Progress (Percent)') + .get('/progress-percent/MacroPower/milestone-test/2.json') + .expectBadge({ + label: 'openWithOneOpenOneClosedIssue', + message: isIntegerPercentage, + link: [`https://github.com/MacroPower/milestone-test/milestone/2`], + }) + +t.create('Milestones (repo or milestone not found)') + .get('/progress/badges/helmets/1.json') + .expectBadge({ + label: 'milestones', + message: 'repo or milestone not found', + }) diff --git a/services/github/github-milestone.service.js b/services/github/github-milestone.service.js new file mode 100644 index 0000000000..9103f73790 --- /dev/null +++ b/services/github/github-milestone.service.js @@ -0,0 +1,93 @@ +'use strict' + +const Joi = require('@hapi/joi') +const { metric } = require('../text-formatters') +const { GithubAuthV3Service } = require('./github-auth-service') +const { documentation, errorMessagesFor } = require('./github-helpers') + +const schema = Joi.array() + .items( + Joi.object({ + state: Joi.string().required(), + }) + ) + .required() + +module.exports = class GithubMilestone extends GithubAuthV3Service { + static get category() { + return 'issue-tracking' + } + + static get route() { + return { + base: 'github/milestones', + pattern: ':variant(open|closed|all)/:user/:repo', + } + } + + static get examples() { + return [ + { + title: 'GitHub milestones', + namedParams: { + user: 'badges', + repo: 'shields', + variant: 'open', + }, + staticPreview: { + label: 'milestones', + message: '2', + color: 'red', + }, + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { + label: 'milestones', + color: 'informational', + } + } + + static render({ user, repo, variant, milestones }) { + const milestoneLength = milestones.length + let color + let label = '' + + switch (variant) { + case 'all': + color = 'blue' + break + case 'open': + color = 'red' + label = 'active' + break + case 'closed': + color = 'green' + label = 'completed' + break + } + + return { + label: `${label} milestones`, + message: metric(milestoneLength), + color, + link: [`https://github.com/${user}/${repo}/milestones`], + } + } + + async fetch({ user, repo, variant }) { + return this._requestJson({ + url: `/repos/${user}/${repo}/milestones?state=${variant}`, + schema, + errorMessages: errorMessagesFor(`repo not found`), + }) + } + + async handle({ user, repo, variant }) { + const milestones = await this.fetch({ user, repo, variant }) + return this.constructor.render({ user, repo, variant, milestones }) + } +} diff --git a/services/github/github-milestone.tester.js b/services/github/github-milestone.tester.js new file mode 100644 index 0000000000..d219ea91db --- /dev/null +++ b/services/github/github-milestone.tester.js @@ -0,0 +1,35 @@ +'use strict' + +const { isMetric } = require('../test-validators') +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('All Milestones') + .get('/all/MacroPower/milestone-test.json') + .expectBadge({ + label: 'milestones', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestones`], + }) + +t.create('Open Milestones') + .get('/open/MacroPower/milestone-test.json') + .expectBadge({ + label: 'active milestones', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestones`], + }) + +t.create('Closed Milestones') + .get('/closed/MacroPower/milestone-test.json') + .expectBadge({ + label: 'completed milestones', + message: isMetric, + link: [`https://github.com/MacroPower/milestone-test/milestones`], + }) + +t.create('Milestones (repo not found)') + .get('/all/badges/helmets.json') + .expectBadge({ + label: 'milestones', + message: 'repo not found', + }) diff --git a/services/test-validators.js b/services/test-validators.js index d25ecf87ce..72e1378aba 100644 --- a/services/test-validators.js +++ b/services/test-validators.js @@ -68,6 +68,10 @@ const isMetricOpenIssues = withRegex( /^([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY]) open$/ ) +const isMetricOverMetric = withRegex( + /^([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY])\/([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY])$/ +) + const isMetricOverTimePeriod = withRegex( /^([1-9][0-9]*[kMGTPEZY]?|[1-9]\.[1-9][kMGTPEZY])\/(year|month|four weeks|week|day)$/ ) @@ -144,6 +148,7 @@ module.exports = { isStarRating, isMetric, isMetricOpenIssues, + isMetricOverMetric, isMetricOverTimePeriod, isPercentage, isIntegerPercentage,