Repository: badges/shields
Pull request #3133 (2019-03-02 06:06:01)

Refactor [waffle]

shields-ci: 
<!--
  0 failure: 
  0 warning: 
  1 messages
  
  DangerID: danger-id-default;
-->



<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Messages</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:book:</td>
      <td>:sparkles: Thanks for your contribution to Shields, @calebcartwright!</td>
    </tr>
  </tbody>
</table>


<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against bf70883a8a2947630b7a27f294868b4689cb8fb9
</p>

chris48s: I've not looked at it in a lot of detail, but based on the error messages, I expect this needs to be updated to account for https://github.com/badges/shields/pull/3125 in order to get the tests to pass
calebcartwright: Yup! Will update this later today

Patch:
diff --git a/services/waffle/waffle-label-redirect.service.js b/services/waffle/waffle-label-redirect.service.js
new file mode 100644
index 0000000000..0275bc1d5c
--- /dev/null
+++ b/services/waffle/waffle-label-redirect.service.js
@@ -0,0 +1,21 @@
+'use strict'
+
+const { redirector } = require('..')
+
+module.exports = [
+  // The WaffleLabel service originally had 'label' as an optional parameter
+  // with a default value of 'ready'. However, after some discussion it was
+  // agreed that 'ready' didn't make sense as a default, nor was there any other
+  // value that would be a reasonable default. As such, 'label' is now a required
+  // param and this redirect service maintains backwards compatibility.
+  // See https://github.com/badges/shields/pull/3133#discussion_r261882687
+  redirector({
+    category: 'issue-tracking',
+    route: {
+      base: 'waffle/label',
+      pattern: ':user/:repo',
+    },
+    transformPath: ({ user, repo }) => `/waffle/label/${user}/${repo}/ready`,
+    dateAdded: new Date('2019-03-05'),
+  }),
+]
diff --git a/services/waffle/waffle-label.service.js b/services/waffle/waffle-label.service.js
new file mode 100644
index 0000000000..3279e482fe
--- /dev/null
+++ b/services/waffle/waffle-label.service.js
@@ -0,0 +1,102 @@
+'use strict'
+
+const Joi = require('joi')
+const { BaseJsonService } = require('..')
+const { metric } = require('../text-formatters')
+const { nonNegativeInteger } = require('../validators')
+
+const schema = Joi.array()
+  .items(
+    Joi.object({
+      label: Joi.object({
+        name: Joi.string().required(),
+        color: Joi.string().hex(),
+      }).allow(null),
+      count: nonNegativeInteger,
+    })
+  )
+  .required()
+
+module.exports = class WaffleLabel extends BaseJsonService {
+  static get category() {
+    return 'issue-tracking'
+  }
+
+  static get route() {
+    return {
+      base: 'waffle/label',
+      pattern: ':user/:repo/:label',
+    }
+  }
+
+  static get defaultBadgeData() {
+    return { label: 'waffle' }
+  }
+
+  static render({ label, color, count }) {
+    if (count === 'absent') {
+      return { message: count }
+    }
+    if (count === 0 || !color) {
+      color = '78bdf2'
+    }
+    return {
+      label,
+      message: metric(count),
+      color,
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'Waffle.io Label',
+        namedParams: {
+          user: 'ritwickdey',
+          repo: 'vscode-live-server',
+          label: 'closed',
+        },
+        staticPreview: this.render({
+          label: 'closed',
+          count: 2145,
+          color: 'A3D3F8',
+        }),
+      },
+    ]
+  }
+
+  async fetch({ user, repo }) {
+    const url = `https://api.waffle.io/${user}/${repo}/columns`
+    return this._requestJson({
+      schema,
+      url,
+      options: { qs: { with: 'count' } },
+      errorMessages: {
+        404: 'project not found',
+      },
+    })
+  }
+
+  transform({ json, label }) {
+    if (json.length === 0) {
+      return { count: 'absent' }
+    }
+
+    for (const col of json) {
+      if (col.label && col.label.name === label) {
+        return {
+          count: col.count,
+          color: col.label.color,
+        }
+      }
+    }
+
+    return { count: 0 }
+  }
+
+  async handle({ user, repo, label }) {
+    const json = await this.fetch({ user, repo })
+    const { count, color } = this.transform({ json, label })
+    return this.constructor.render({ label, color, count })
+  }
+}
diff --git a/services/waffle/waffle-label.spec.js b/services/waffle/waffle-label.spec.js
new file mode 100644
index 0000000000..527df5b834
--- /dev/null
+++ b/services/waffle/waffle-label.spec.js
@@ -0,0 +1,59 @@
+'use strict'
+
+const { test, given } = require('sazerac')
+const { metric } = require('../text-formatters')
+const WaffleLabel = require('./waffle-label.service')
+
+const fakeData = [
+  {
+    label: null,
+    count: 20,
+  },
+  {
+    count: 10,
+  },
+  {
+    label: {
+      color: 'c5def5',
+      name: 'feature',
+    },
+    count: 3,
+  },
+  {
+    label: {
+      name: 'bug',
+      color: 'fbca04',
+    },
+    count: 5,
+  },
+]
+
+describe('WaffleLabel', function() {
+  test(WaffleLabel.render, () => {
+    given({ count: 'absent' }).expect({ message: 'absent' })
+    given({ count: 0, label: 'feature' }).expect({
+      message: '0',
+      color: '78bdf2',
+      label: 'feature',
+    })
+    given({ count: 1007, color: 'fbca04', label: 'bug' }).expect({
+      message: metric(1007),
+      color: 'fbca04',
+      label: 'bug',
+    })
+    given({ count: 123, label: 'task' }).expect({
+      message: metric(123),
+      color: '78bdf2',
+      label: 'task',
+    })
+  })
+
+  test(WaffleLabel.prototype.transform, () => {
+    given({ json: [] }).expect({ count: 'absent' })
+    given({ json: fakeData, label: 'not-there' }).expect({ count: 0 })
+    given({ json: fakeData, label: 'bug' }).expect({
+      count: 5,
+      color: 'fbca04',
+    })
+  })
+})
diff --git a/services/waffle/waffle.service.js b/services/waffle/waffle.service.js
deleted file mode 100644
index 1e7dc409e0..0000000000
--- a/services/waffle/waffle.service.js
+++ /dev/null
@@ -1,93 +0,0 @@
-'use strict'
-
-const LegacyService = require('../legacy-service')
-const {
-  makeBadgeData: getBadgeData,
-  makeLabel: getLabel,
-} = require('../../lib/badge-data')
-const { checkErrorResponse } = require('../../lib/error-helper')
-
-// 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.
-module.exports = class Waffle extends LegacyService {
-  static get category() {
-    return 'issue-tracking'
-  }
-
-  static get route() {
-    return {
-      base: 'waffle/label',
-      pattern: ':user/:repo/:query',
-    }
-  }
-
-  static get examples() {
-    return [
-      {
-        title: 'Waffle.io',
-        namedParams: {
-          user: 'evancohen',
-          repo: 'smart-mirror',
-          query: 'status: in progress',
-        },
-        staticPreview: {
-          label: 'status: in progress',
-          message: '2',
-          color: '000',
-        },
-      },
-    ]
-  }
-
-  static registerLegacyRouteHandler({ camp, cache }) {
-    camp.route(
-      /^\/waffle\/label\/([^/]+)\/([^/]+)\/?([^/]+)?\.(svg|png|gif|jpg|json)$/,
-      cache((data, match, sendBadge, request) => {
-        const user = match[1] // eg, evancohen
-        const repo = match[2] // eg, smart-mirror
-        const ghLabel = match[3] || 'ready' // eg, in%20progress
-        const format = match[4]
-        const apiUrl = `https://api.waffle.io/${user}/${repo}/columns?with=count`
-        const badgeData = getBadgeData('waffle', data)
-
-        request(apiUrl, (err, res, buffer) => {
-          try {
-            if (checkErrorResponse(badgeData, err, res)) {
-              sendBadge(format, badgeData)
-              return
-            }
-            const cols = JSON.parse(buffer)
-            if (cols.length === 0) {
-              badgeData.text[1] = 'absent'
-              sendBadge(format, badgeData)
-              return
-            }
-            let count = 0
-            let color = '78bdf2'
-            for (let i = 0; i < cols.length; i++) {
-              if ('label' in cols[i] && cols[i].label !== null) {
-                if (cols[i].label.name === ghLabel) {
-                  count = cols[i].count
-                  color = cols[i].label.color
-                  break
-                }
-              }
-            }
-            badgeData.text[0] = getLabel(ghLabel, data)
-            badgeData.text[1] = `${count}`
-            badgeData.colorscheme = undefined
-            badgeData.colorB = data.colorB || color
-            sendBadge(format, badgeData)
-          } catch (e) {
-            badgeData.text[1] = 'invalid'
-            sendBadge(format, badgeData)
-          }
-        })
-      })
-    )
-  }
-}


Test patch:
diff --git a/services/waffle/waffle-label-redirect.tester.js b/services/waffle/waffle-label-redirect.tester.js
new file mode 100644
index 0000000000..6abd1ea145
--- /dev/null
+++ b/services/waffle/waffle-label-redirect.tester.js
@@ -0,0 +1,16 @@
+'use strict'
+
+const { ServiceTester } = require('../tester')
+
+const t = (module.exports = new ServiceTester({
+  id: 'WaffleLabelRedirect',
+  title: 'WaffleLabelRedirect',
+  pathPrefix: '/waffle/label',
+}))
+
+t.create('waffle label redirect')
+  .get('/waffleio/waffle.io.svg', {
+    followRedirect: false,
+  })
+  .expectStatus(301)
+  .expectHeader('Location', '/waffle/label/waffleio/waffle.io/ready.svg')
diff --git a/services/waffle/waffle-label.tester.js b/services/waffle/waffle-label.tester.js
new file mode 100644
index 0000000000..c7f8f02fc1
--- /dev/null
+++ b/services/waffle/waffle-label.tester.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const t = (module.exports = require('../tester').createServiceTester())
+const { nonNegativeInteger } = require('../validators')
+
+t.create('project not found)')
+  .get('/not-a-user/not-a-repo/bug.json')
+  .expectBadge({
+    label: 'waffle',
+    message: 'project not found',
+  })
+
+t.create('label not found')
+  .get(
+    '/ritwickdey/vscode-live-server/not-a-real-label.json?style=_shields_test'
+  )
+  .expectBadge({
+    label: 'not-a-real-label',
+    message: '0',
+    color: '#78bdf2',
+  })
+
+t.create('specified label found')
+  .get('/ritwickdey/vscode-live-server/bug.json')
+  .expectBadge({
+    label: 'bug',
+    message: nonNegativeInteger,
+  })
diff --git a/services/waffle/waffle.tester.js b/services/waffle/waffle.tester.js
deleted file mode 100644
index f19e3cda12..0000000000
--- a/services/waffle/waffle.tester.js
+++ /dev/null
@@ -1,96 +0,0 @@
-'use strict'
-
-const Joi = require('joi')
-const { invalidJSON } = require('../response-fixtures')
-const t = (module.exports = require('../tester').createServiceTester())
-
-const fakeData = [
-  {
-    label: null,
-    count: 20,
-  },
-  {
-    count: 10,
-  },
-  {
-    label: {
-      color: 'c5def5',
-      name: 'feature',
-    },
-    count: 3,
-  },
-  {
-    label: {
-      name: 'bug',
-      color: 'fbca04',
-    },
-    count: 5,
-  },
-]
-
-t.create(
-  'label should be `bug` & value should be exactly 5 as supplied in `fakeData`.  e.g: bug|5'
-)
-  .get('/userName/repoName/bug.json?style=_shields_test')
-  .intercept(nock =>
-    nock('https://api.waffle.io/')
-      .get('/userName/repoName/columns?with=count')
-      .reply(200, fakeData)
-  )
-  .expectBadge({
-    label: 'bug',
-    message: '5',
-    color: '#fbca04',
-  })
-
-t.create('label should be `Mybug` & value should be formatted.  e.g: Mybug|25')
-  .get('/ritwickdey/vscode-live-server/bug.json?label=Mybug')
-  .expectBadge({
-    label: 'Mybug',
-    message: Joi.number()
-      .integer()
-      .positive(),
-  })
-
-t.create('label (repo not found)')
-  .get('/not-a-user/not-a-repo/bug.json')
-  .expectBadge({
-    label: 'waffle',
-    message: 'not found',
-  })
-
-t.create('label (label not found)')
-  .get(
-    '/ritwickdey/vscode-live-server/not-a-real-label.json?style=_shields_test'
-  )
-  .expectBadge({
-    label: 'not-a-real-label',
-    message: '0',
-    color: '#78bdf2',
-  })
-
-t.create('label (empty response)')
-  .get('/userName/repoName/bug.json')
-  .intercept(nock =>
-    nock('https://api.waffle.io/')
-      .get('/userName/repoName/columns?with=count')
-      .reply(200, [])
-  )
-  .expectBadge({
-    label: 'waffle',
-    message: 'absent',
-  })
-
-t.create('label (connection error)')
-  .get('/ritwickdey/vscode-live-server/bug.json')
-  .networkOff()
-  .expectBadge({ label: 'waffle', message: 'inaccessible' })
-
-t.create('label (unexpected response)')
-  .get('/userName/repoName/bug.json')
-  .intercept(nock =>
-    nock('https://api.waffle.io/')
-      .get('/userName/repoName/columns?with=count')
-      .reply(invalidJSON)
-  )
-  .expectBadge({ label: 'waffle', message: 'invalid' })