{"action":"created","comment":{"url":"https://api.github.com/repos/badges/shields/pulls/comments/244656123","pull_request_review_id":188576848,"id":244656123,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0NDY1NjEyMw==","diff_hunk":"@@ -1,92 +1,111 @@\n 'use strict'\n \n-const LegacyService = require('../legacy-service')\n-const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')\n-const {\n-  coveragePercentage: coveragePercentageColor,\n-} = require('../../lib/color-formatters')\n+const Joi = require('joi')\n+const { InvalidResponse } = require('../errors')\n+const TeamCityBase = require('./teamcity-base')\n+const { coveragePercentage } = require('../../lib/color-formatters')\n+\n+const buildStatisticsSchema = Joi.object({\n+  property: Joi.array()\n+    .items(\n+      Joi.object({\n+        name: Joi.string().required(),\n+        value: Joi.string().required(),\n+      })\n+    )\n+    .required(),\n+}).required()\n+\n+module.exports = class TeamCityCoverage extends TeamCityBase {\n+  static render({ coverage }) {\n+    return {\n+      message: `${coverage.toFixed(0)}%`,\n+      color: coveragePercentage(coverage),\n+    }\n+  }\n+\n+  static get defaultBadgeData() {\n+    return {\n+      label: 'coverage',\n+    }\n+  }\n \n-// This legacy service should be rewritten to use e.g. BaseJsonService.\n-//\n-// Tips for rewriting:\n-// https://github.com/badges/shields/blob/master/doc/rewriting-services.md\n-//\n-// Do not base new services on this code.\n-module.exports = class TeamcityCoverage extends LegacyService {\n   static get category() {\n     return 'quality'\n   }\n \n   static get route() {\n     return {\n       base: 'teamcity/coverage',\n-      pattern: ':buildType',\n+      format: '(?:(http|https)/(.+)/)?([^/]+)',\n+      capture: ['protocol', 'hostAndPath', 'buildId'],\n     }\n   }\n \n   static get examples() {\n     return [\n       {\n-        title: 'TeamCity CodeBetter Coverage',\n-        namedParams: { buildType: 'bt428' },\n-        staticPreview: { message: '55%', color: 'yellow' },\n+        title: 'TeamCity Coverage (CodeBetter)',\n+        pattern: ':buildId',\n+        namedParams: {\n+          buildId: 'bt428',\n+        },\n+        staticPreview: this.render({\n+          coverage: 82,\n+        }),\n+      },\n+      {\n+        title: 'TeamCity Coverage',\n+        pattern: ':protocol/:hostAndPath/s/:buildId',\n+        namedParams: {\n+          protocol: 'https',\n+          hostAndPath: 'https/teamcity.jetbrains.com',\n+          buildId: 'bt428',\n+        },\n+        staticPreview: this.render({\n+          coverage: 95,\n+        }),\n       },\n     ]\n   }\n \n-  static get defaultBadgeData() {\n-    return {\n-      label: 'coverage',\n-    }\n+  async handle({ protocol, hostAndPath, buildId }) {\n+    // JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-Statistics\n+    const buildLocator = `buildType:(id:${buildId})`\n+    const apiPath = `app/rest/builds/${encodeURIComponent(\n+      buildLocator\n+    )}/statistics`\n+    const data = await this.fetch({\n+      protocol,\n+      hostAndPath,\n+      apiPath,\n+      schema: buildStatisticsSchema,\n+    })\n+\n+    const { coverage } = this.transform({ data })\n+    return this.constructor.render({ coverage })\n   }\n \n-  static registerLegacyRouteHandler({ camp, cache }) {\n-    camp.route(\n-      /^\\/teamcity\\/coverage\\/(.*)\\.(svg|png|gif|jpg|json)$/,\n-      cache((data, match, sendBadge, request) => {\n-        const buildType = match[1] // eg, `bt428`.\n-        const format = match[2]\n-        const apiUrl = `http://teamcity.codebetter.com/app/rest/builds/buildType:(id:${buildType})/statistics?guest=1`\n-        const badgeData = getBadgeData('coverage', data)\n-        request(\n-          apiUrl,\n-          { headers: { Accept: 'application/json' } },\n-          (err, res, buffer) => {\n-            if (err != null) {\n-              badgeData.text[1] = 'inaccessible'\n-              sendBadge(format, badgeData)\n-              return\n-            }\n-            try {\n-              const data = JSON.parse(buffer)\n-              let covered\n-              let total\n+  transform({ data }) {\n+    let covered, total\n \n-              data.property.forEach(property => {\n-                if (property.name === 'CodeCoverageAbsSCovered') {\n-                  covered = property.value\n-                } else if (property.name === 'CodeCoverageAbsSTotal') {\n-                  total = property.value\n-                }\n-              })\n+    for (const p of data.property) {\n+      if (p.name === 'CodeCoverageAbsSCovered') {\n+        covered = +p.value\n+      } else if (p.name === 'CodeCoverageAbsSTotal') {\n+        total = +p.value\n+      }\n \n-              if (covered === undefined || total === undefined) {\n-                badgeData.text[1] = 'malformed'\n-                sendBadge(format, badgeData)\n-                return\n-              }\n+      if (covered !== undefined && total !== undefined) {\n+        break","path":"services/teamcity/teamcity-coverage.service.js","position":153,"original_position":153,"commit_id":"f1faf08d35d23b68adc57d9ae7542463daf16bfa","original_commit_id":"f1faf08d35d23b68adc57d9ae7542463daf16bfa","user":{"login":"paulmelnikow","id":1487036,"node_id":"MDQ6VXNlcjE0ODcwMzY=","avatar_url":"https://avatars2.githubusercontent.com/u/1487036?v=4","gravatar_id":"","url":"https://api.github.com/users/paulmelnikow","html_url":"https://github.com/paulmelnikow","followers_url":"https://api.github.com/users/paulmelnikow/followers","following_url":"https://api.github.com/users/paulmelnikow/following{/other_user}","gists_url":"https://api.github.com/users/paulmelnikow/gists{/gist_id}","starred_url":"https://api.github.com/users/paulmelnikow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paulmelnikow/subscriptions","organizations_url":"https://api.github.com/users/paulmelnikow/orgs","repos_url":"https://api.github.com/users/paulmelnikow/repos","events_url":"https://api.github.com/users/paulmelnikow/events{/privacy}","received_events_url":"https://api.github.com/users/paulmelnikow/received_events","type":"User","site_admin":false},"body":"You could simplify the logic by moving the return statement here, and then unconditionally throw if the loop completes without reaching this.","created_at":"2019-01-02T02:38:47Z","updated_at":"2019-01-02T02:39:23Z","html_url":"https://github.com/badges/shields/pull/2601#discussion_r244656123","pull_request_url":"https://api.github.com/repos/badges/shields/pulls/2601","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/badges/shields/pulls/comments/244656123"},"html":{"href":"https://github.com/badges/shields/pull/2601#discussion_r244656123"},"pull_request":{"href":"https://api.github.com/repos/badges/shields/pulls/2601"}}},"pull_request":{"url":"https://api.github.com/repos/badges/shields/pulls/2601","id":241270428,"node_id":"MDExOlB1bGxSZXF1ZXN0MjQxMjcwNDI4","html_url":"https://github.com/badges/shields/pull/2601","diff_url":"https://github.com/badges/shields/pull/2601.diff","patch_url":"https://github.com/badges/shields/pull/2601.patch","issue_url":"https://api.github.com/repos/badges/shields/issues/2601","number":2601,"state":"open","locked":false,"title":"refactor [TeamCity] ","user":{"login":"calebcartwright","id":13042488,"node_id":"MDQ6VXNlcjEzMDQyNDg4","avatar_url":"https://avatars0.githubusercontent.com/u/13042488?v=4","gravatar_id":"","url":"https://api.github.com/users/calebcartwright","html_url":"https://github.com/calebcartwright","followers_url":"https://api.github.com/users/calebcartwright/followers","following_url":"https://api.github.com/users/calebcartwright/following{/other_user}","gists_url":"https://api.github.com/users/calebcartwright/gists{/gist_id}","starred_url":"https://api.github.com/users/calebcartwright/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calebcartwright/subscriptions","organizations_url":"https://api.github.com/users/calebcartwright/orgs","repos_url":"https://api.github.com/users/calebcartwright/repos","events_url":"https://api.github.com/users/calebcartwright/events{/privacy}","received_events_url":"https://api.github.com/users/calebcartwright/received_events","type":"User","site_admin":false},"body":"Refactors TeamCity services to new service model and adds tests.\r\n\r\nRefs #1358 ","created_at":"2018-12-28T06:00:48Z","updated_at":"2019-01-02T02:39:23Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5312489d1ea3e885cb85234da94965293a806eef","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":316395868,"node_id":"MDU6TGFiZWwzMTYzOTU4Njg=","url":"https://api.github.com/repos/badges/shields/labels/service-badge","name":"service-badge","color":"fbca04","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/badges/shields/pulls/2601/commits","review_comments_url":"https://api.github.com/repos/badges/shields/pulls/2601/comments","review_comment_url":"https://api.github.com/repos/badges/shields/pulls/comments{/number}","comments_url":"https://api.github.com/repos/badges/shields/issues/2601/comments","statuses_url":"https://api.github.com/repos/badges/shields/statuses/f1faf08d35d23b68adc57d9ae7542463daf16bfa","head":{"label":"badges:teamcity-refactor","ref":"teamcity-refactor","sha":"f1faf08d35d23b68adc57d9ae7542463daf16bfa","user":{"login":"badges","id":6254238,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNTQyMzg=","avatar_url":"https://avatars2.githubusercontent.com/u/6254238?v=4","gravatar_id":"","url":"https://api.github.com/users/badges","html_url":"https://github.com/badges","followers_url":"https://api.github.com/users/badges/followers","following_url":"https://api.github.com/users/badges/following{/other_user}","gists_url":"https://api.github.com/users/badges/gists{/gist_id}","starred_url":"https://api.github.com/users/badges/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badges/subscriptions","organizations_url":"https://api.github.com/users/badges/orgs","repos_url":"https://api.github.com/users/badges/repos","events_url":"https://api.github.com/users/badges/events{/privacy}","received_events_url":"https://api.github.com/users/badges/received_events","type":"Organization","site_admin":false},"repo":{"id":7910045,"node_id":"MDEwOlJlcG9zaXRvcnk3OTEwMDQ1","name":"shields","full_name":"badges/shields","private":false,"owner":{"login":"badges","id":6254238,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNTQyMzg=","avatar_url":"https://avatars2.githubusercontent.com/u/6254238?v=4","gravatar_id":"","url":"https://api.github.com/users/badges","html_url":"https://github.com/badges","followers_url":"https://api.github.com/users/badges/followers","following_url":"https://api.github.com/users/badges/following{/other_user}","gists_url":"https://api.github.com/users/badges/gists{/gist_id}","starred_url":"https://api.github.com/users/badges/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badges/subscriptions","organizations_url":"https://api.github.com/users/badges/orgs","repos_url":"https://api.github.com/users/badges/repos","events_url":"https://api.github.com/users/badges/events{/privacy}","received_events_url":"https://api.github.com/users/badges/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/badges/shields","description":"Concise, consistent, and legible badges in SVG and raster format","fork":false,"url":"https://api.github.com/repos/badges/shields","forks_url":"https://api.github.com/repos/badges/shields/forks","keys_url":"https://api.github.com/repos/badges/shields/keys{/key_id}","collaborators_url":"https://api.github.com/repos/badges/shields/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/badges/shields/teams","hooks_url":"https://api.github.com/repos/badges/shields/hooks","issue_events_url":"https://api.github.com/repos/badges/shields/issues/events{/number}","events_url":"https://api.github.com/repos/badges/shields/events","assignees_url":"https://api.github.com/repos/badges/shields/assignees{/user}","branches_url":"https://api.github.com/repos/badges/shields/branches{/branch}","tags_url":"https://api.github.com/repos/badges/shields/tags","blobs_url":"https://api.github.com/repos/badges/shields/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/badges/shields/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/badges/shields/git/refs{/sha}","trees_url":"https://api.github.com/repos/badges/shields/git/trees{/sha}","statuses_url":"https://api.github.com/repos/badges/shields/statuses/{sha}","languages_url":"https://api.github.com/repos/badges/shields/languages","stargazers_url":"https://api.github.com/repos/badges/shields/stargazers","contributors_url":"https://api.github.com/repos/badges/shields/contributors","subscribers_url":"https://api.github.com/repos/badges/shields/subscribers","subscription_url":"https://api.github.com/repos/badges/shields/subscription","commits_url":"https://api.github.com/repos/badges/shields/commits{/sha}","git_commits_url":"https://api.github.com/repos/badges/shields/git/commits{/sha}","comments_url":"https://api.github.com/repos/badges/shields/comments{/number}","issue_comment_url":"https://api.github.com/repos/badges/shields/issues/comments{/number}","contents_url":"https://api.github.com/repos/badges/shields/contents/{+path}","compare_url":"https://api.github.com/repos/badges/shields/compare/{base}...{head}","merges_url":"https://api.github.com/repos/badges/shields/merges","archive_url":"https://api.github.com/repos/badges/shields/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/badges/shields/downloads","issues_url":"https://api.github.com/repos/badges/shields/issues{/number}","pulls_url":"https://api.github.com/repos/badges/shields/pulls{/number}","milestones_url":"https://api.github.com/repos/badges/shields/milestones{/number}","notifications_url":"https://api.github.com/repos/badges/shields/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/badges/shields/labels{/name}","releases_url":"https://api.github.com/repos/badges/shields/releases{/id}","deployments_url":"https://api.github.com/repos/badges/shields/deployments","created_at":"2013-01-30T07:41:54Z","updated_at":"2019-01-02T02:22:16Z","pushed_at":"2019-01-02T02:28:39Z","git_url":"git://github.com/badges/shields.git","ssh_url":"git@github.com:badges/shields.git","clone_url":"https://github.com/badges/shields.git","svn_url":"https://github.com/badges/shields","homepage":"https://shields.io","size":18056,"stargazers_count":6899,"watchers_count":6899,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":1610,"mirror_url":null,"archived":false,"open_issues_count":197,"license":{"key":"cc0-1.0","name":"Creative Commons Zero v1.0 Universal","spdx_id":"CC0-1.0","url":"https://api.github.com/licenses/cc0-1.0","node_id":"MDc6TGljZW5zZTY="},"forks":1610,"open_issues":197,"watchers":6899,"default_branch":"master"}},"base":{"label":"badges:master","ref":"master","sha":"02151cb7005f4f0671565e3ef0863e6361999039","user":{"login":"badges","id":6254238,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNTQyMzg=","avatar_url":"https://avatars2.githubusercontent.com/u/6254238?v=4","gravatar_id":"","url":"https://api.github.com/users/badges","html_url":"https://github.com/badges","followers_url":"https://api.github.com/users/badges/followers","following_url":"https://api.github.com/users/badges/following{/other_user}","gists_url":"https://api.github.com/users/badges/gists{/gist_id}","starred_url":"https://api.github.com/users/badges/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badges/subscriptions","organizations_url":"https://api.github.com/users/badges/orgs","repos_url":"https://api.github.com/users/badges/repos","events_url":"https://api.github.com/users/badges/events{/privacy}","received_events_url":"https://api.github.com/users/badges/received_events","type":"Organization","site_admin":false},"repo":{"id":7910045,"node_id":"MDEwOlJlcG9zaXRvcnk3OTEwMDQ1","name":"shields","full_name":"badges/shields","private":false,"owner":{"login":"badges","id":6254238,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYyNTQyMzg=","avatar_url":"https://avatars2.githubusercontent.com/u/6254238?v=4","gravatar_id":"","url":"https://api.github.com/users/badges","html_url":"https://github.com/badges","followers_url":"https://api.github.com/users/badges/followers","following_url":"https://api.github.com/users/badges/following{/other_user}","gists_url":"https://api.github.com/users/badges/gists{/gist_id}","starred_url":"https://api.github.com/users/badges/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/badges/subscriptions","organizations_url":"https://api.github.com/users/badges/orgs","repos_url":"https://api.github.com/users/badges/repos","events_url":"https://api.github.com/users/badges/events{/privacy}","received_events_url":"https://api.github.com/users/badges/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/badges/shields","description":"Concise, consistent, and legible badges in SVG and raster format","fork":false,"url":"https://api.github.com/repos/badges/shields","forks_url":"https://api.github.com/repos/badges/shields/forks","keys_url":"https://api.github.com/repos/badges/shields/keys{/key_id}","collaborators_url":"https://api.github.com/repos/badges/shields/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/badges/shields/teams","hooks_url":"https://api.github.com/repos/badges/shields/hooks","issue_events_url":"https://api.github.com/repos/badges/shields/issues/events{/number}","events_url":"https://api.github.com/repos/badges/shields/events","assignees_url":"https://api.github.com/repos/badges/shields/assignees{/user}","branches_url":"https://api.github.com/repos/badges/shields/branches{/branch}","tags_url":"https://api.github.com/repos/badges/shields/tags","blobs_url":"https://api.github.com/repos/badges/shields/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/badges/shields/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/badges/shields/git/refs{/sha}","trees_url":"https://api.github.com/repos/badges/shields/git/trees{/sha}","statuses_url":"https://api.github.com/repos/badges/shields/statuses/{sha}","languages_url":"https://api.github.com/repos/badges/shields/languages","stargazers_url":"https://api.github.com/repos/badges/shields/stargazers","contributors_url":"https://api.github.com/repos/badges/shields/contributors","subscribers_url":"https://api.github.com/repos/badges/shields/subscribers","subscription_url":"https://api.github.com/repos/badges/shields/subscription","commits_url":"https://api.github.com/repos/badges/shields/commits{/sha}","git_commits_url":"https://api.github.com/repos/badges/shields/git/commits{/sha}","comments_url":"https://api.github.com/repos/badges/shields/comments{/number}","issue_comment_url":"https://api.github.com/repos/badges/shields/issues/comments{/number}","contents_url":"https://api.github.com/repos/badges/shields/contents/{+path}","compare_url":"https://api.github.com/repos/badges/shields/compare/{base}...{head}","merges_url":"https://api.github.com/repos/badges/shields/merges","archive_url":"https://api.github.com/repos/badges/shields/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/badges/shields/downloads","issues_url":"https://api.github.com/repos/badges/shields/issues{/number}","pulls_url":"https://api.github.com/repos/badges/shields/pulls{/number}","milestones_url":"https://api.github.com/repos/badges/shields/milestones{/number}","notifications_url":"https://api.github.com/repos/badges/shields/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/badges/shields/labels{/name}","releases_url":"https://api.github.com/repos/badges/shields/releases{/id}","deployments_url":"https://api.github.com/repos/badges/shields/deployments","created_at":"2013-01-30T07:41:54Z","updated_at":"2019-01-02T02:22:16Z","pushed_at":"2019-01-02T02:28:39Z","git_url":"git://github.com/badges/shields.git","ssh_url":"git@github.com:badges/shields.git","clone_url":"https://github.com/badges/shields.git","svn_url":"https://github.com/badges/shields","homepage":"https://shields.io","size":18056,"stargazers_count":6899,"watchers_count":6899,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":1610,"mirror_url":null,"archived":false,"open_issues_count":197,"license":{"key":"cc0-1.0","name":"Creative Commons Zero v1.0 Universal","spdx_id":"CC0-1.0","url":"https://api.github.com/licenses/cc0-1.0","node_id":"MDc6TGljZW5zZTY="},"forks":1610,"open_issues":197,"watchers":6899,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/badges/shields/pulls/2601"},"html":{"href":"https://github.com/badges/shields/pull/2601"},"issue":{"href":"https://api.github.com/repos/badges/shields/issues/2601"},"comments":{"href":"https://api.github.com/repos/badges/shields/issues/2601/comments"},"review_comments":{"href":"https://api.github.com/repos/badges/shields/pulls/2601/comments"},"review_comment":{"href":"https://api.github.com/repos/badges/shields/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/badges/shields/pulls/2601/commits"},"statuses":{"href":"https://api.github.com/repos/badges/shields/statuses/f1faf08d35d23b68adc57d9ae7542463daf16bfa"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"paulmelnikow"}}