diff --git a/.eslintrc.yml b/.eslintrc.yml index 509cabf1dcf907572be9b700f7e42665189bd4e3..d9f322d1c7b607b6774192c34b58a5221ed0f32b 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -12,15 +12,40 @@ parserOptions: sourceType: 'script' overrides: - files: - - gatsby-browser.js - parserOptions: - sourceType: 'module' + - files: + - gatsby-browser.js + parserOptions: + sourceType: 'module' + - files: + - 'core/base-service/**/*.js' + - 'services/**/*.js' + rules: + sort-class-members/sort-class-members: + [ + 'warn', + { + order: + [ + 'name', + 'category', + 'isDeprecated', + 'route', + 'examples', + 'defaultBadgeData', + 'render', + 'constructor', + 'fetch', + 'transform', + 'handle', + ], + }, + ] plugins: - mocha - no-extension-in-require - - 'chai-friendly' + - chai-friendly + - sort-class-members rules: # Disable some rules from eslint:recommended. diff --git a/core/base-service/base.js b/core/base-service/base.js index f4c4f0f58f0576cdc936810206db1dd6f46feb89..07c9b219a12f758b49d2305bdb3f6732b0df97a2 100644 --- a/core/base-service/base.js +++ b/core/base-service/base.js @@ -70,26 +70,6 @@ const serviceDataSchema = Joi.object({ .required() module.exports = class BaseService { - constructor({ sendAndCacheRequest }, { handleInternalErrors }) { - this._requestFetcher = sendAndCacheRequest - this._handleInternalErrors = handleInternalErrors - } - - static render(props) { - throw new Error(`render() function not implemented for ${this.name}`) - } - - /** - * Asynchronous function to handle requests for this service. Take the route - * parameters (as defined in the `route` property), perform a request using - * `this._sendAndCacheRequest`, and return the badge data. - */ - async handle(namedParams, queryParams) { - throw new Error(`Handler not implemented for ${this.constructor.name}`) - } - - // Metadata - /** * Name of the category to sort this badge into (eg. "build"). Used to sort * the badges on the main shields.io website. @@ -98,6 +78,10 @@ module.exports = class BaseService { throw new Error(`Category not set for ${this.name}`) } + static get isDeprecated() { + return false + } + /** * Returns an object: * - base: (Optional) The base path of the routes for this service. This is @@ -127,17 +111,14 @@ module.exports = class BaseService { throw new Error(`Route not defined for ${this.name}`) } - static get isDeprecated() { - return false - } - - /** - * Default data for the badge. Can include label, logo, and color. These - * defaults are used if the value is neither included in the service data - * from the handler nor overridden by the user via query parameters. - */ - static get defaultBadgeData() { - return {} + static get _cacheLength() { + const cacheLengths = { + build: 30, + license: 3600, + version: 300, + debug: 60, + } + return cacheLengths[this.category] } /** @@ -172,6 +153,15 @@ module.exports = class BaseService { return [] } + /** + * Default data for the badge. Can include label, logo, and color. These + * defaults are used if the value is neither included in the service data + * from the handler nor overridden by the user via query parameters. + */ + static get defaultBadgeData() { + return {} + } + static validateDefinition() { assertValidCategory(this.category, `Category for ${this.name}`) @@ -219,14 +209,53 @@ module.exports = class BaseService { return result } - static get _cacheLength() { - const cacheLengths = { - build: 30, - license: 3600, - version: 300, - debug: 60, - } - return cacheLengths[this.category] + static render(props) { + throw new Error(`render() function not implemented for ${this.name}`) + } + + constructor({ sendAndCacheRequest }, { handleInternalErrors }) { + this._requestFetcher = sendAndCacheRequest + this._handleInternalErrors = handleInternalErrors + } + + async _request({ url, options = {}, errorMessages = {} }) { + const logTrace = (...args) => trace.logTrace('fetch', ...args) + logTrace(emojic.bowAndArrow, 'Request', url, '\n', options) + const { res, buffer } = await this._requestFetcher(url, options) + logTrace(emojic.dart, 'Response status code', res.statusCode) + return checkErrorResponse.asPromise(errorMessages)({ buffer, res }) + } + + static _validate( + data, + schema, + { + prettyErrorMessage = 'invalid response data', + includeKeys = false, + allowAndStripUnknownKeys = true, + } = {} + ) { + return validate( + { + ErrorClass: InvalidResponse, + prettyErrorMessage, + includeKeys, + traceErrorMessage: 'Response did not match schema', + traceSuccessMessage: 'Response after validation', + allowAndStripUnknownKeys, + }, + data, + schema + ) + } + + /** + * Asynchronous function to handle requests for this service. Take the route + * parameters (as defined in the `route` property), perform a request using + * `this._sendAndCacheRequest`, and return the badge data. + */ + async handle(namedParams, queryParams) { + throw new Error(`Handler not implemented for ${this.constructor.name}`) } _handleError(error) { @@ -398,35 +427,4 @@ module.exports = class BaseService { }) ) } - - static _validate( - data, - schema, - { - prettyErrorMessage = 'invalid response data', - includeKeys = false, - allowAndStripUnknownKeys = true, - } = {} - ) { - return validate( - { - ErrorClass: InvalidResponse, - prettyErrorMessage, - includeKeys, - traceErrorMessage: 'Response did not match schema', - traceSuccessMessage: 'Response after validation', - allowAndStripUnknownKeys, - }, - data, - schema - ) - } - - async _request({ url, options = {}, errorMessages = {} }) { - const logTrace = (...args) => trace.logTrace('fetch', ...args) - logTrace(emojic.bowAndArrow, 'Request', url, '\n', options) - const { res, buffer } = await this._requestFetcher(url, options) - logTrace(emojic.dart, 'Response status code', res.statusCode) - return checkErrorResponse.asPromise(errorMessages)({ buffer, res }) - } } diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index 8721ab03dd3f51438c83e892b7cde88c82b63986..274daf6ece1d2d75f52071440308cccac9b5a16e 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -25,22 +25,16 @@ const queryParamSchema = Joi.object({ .required() class DummyService extends BaseService { - static render({ namedParamA, queryParamA }) { - return { - message: `Hello namedParamA: ${namedParamA} with queryParamA: ${queryParamA}`, - } - } - - async handle({ namedParamA }, { queryParamA }) { - return this.constructor.render({ namedParamA, queryParamA }) - } - static get category() { return 'other' } - static get defaultBadgeData() { - return { label: 'cat', namedLogo: 'appveyor' } + static get route() { + return { + base: 'foo', + pattern: ':namedParamA', + queryParamSchema, + } } static get examples() { @@ -54,13 +48,19 @@ class DummyService extends BaseService { ] } - static get route() { + static get defaultBadgeData() { + return { label: 'cat', namedLogo: 'appveyor' } + } + + static render({ namedParamA, queryParamA }) { return { - base: 'foo', - pattern: ':namedParamA', - queryParamSchema, + message: `Hello namedParamA: ${namedParamA} with queryParamA: ${queryParamA}`, } } + + async handle({ namedParamA }, { queryParamA }) { + return this.constructor.render({ namedParamA, queryParamA }) + } } describe('BaseService', function() { diff --git a/core/base-service/deprecated-service.js b/core/base-service/deprecated-service.js index c844070f28b12f87254d3d4e2154b1c6e87fb918..8358e1f0f1a78c9bcdccf21006ba7597b72abdd0 100644 --- a/core/base-service/deprecated-service.js +++ b/core/base-service/deprecated-service.js @@ -38,22 +38,22 @@ function deprecatedService(attrs) { return category } - static get route() { - return route - } - static get isDeprecated() { return true } - static get defaultBadgeData() { - return { label } + static get route() { + return route } static get examples() { return examples } + static get defaultBadgeData() { + return { label } + } + async handle() { throw new Deprecated({ prettyMessage: message }) } diff --git a/core/base-service/redirector.js b/core/base-service/redirector.js index e738ce8352c3bc0656be268de464c96b046c0149..490296e4b6c065a257bf07992f2513f4e3ee75b5 100644 --- a/core/base-service/redirector.js +++ b/core/base-service/redirector.js @@ -40,18 +40,6 @@ module.exports = function redirector(attrs) { } = Joi.attempt(attrs, attrSchema, `Redirector for ${attrs.route.base}`) return class Redirector extends BaseService { - static get category() { - return category - } - - static get route() { - return route - } - - static get isDeprecated() { - return true - } - static get name() { if (name) { return name @@ -62,6 +50,18 @@ module.exports = function redirector(attrs) { } } + static get category() { + return category + } + + static get isDeprecated() { + return true + } + + static get route() { + return route + } + static register({ camp, requestCounter }) { const { regex, captureNames } = prepareRoute(this.route) diff --git a/package-lock.json b/package-lock.json index 3999fb0b58b8902c7b6279f521f0b10a5de607c4..21d79f8fd6fc22694384d04cbd4fb5013e111df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8977,6 +8977,12 @@ "integrity": "sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==", "dev": true }, + "eslint-plugin-sort-class-members": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.4.0.tgz", + "integrity": "sha512-FQG6d4Cy2vYmG9gr6J138E91WaltqwOk/b/7GCssPqnUmizrcgOeN91bV5eOTuoS4RSH1Jdn4ukWEtyWLwV8ig==", + "dev": true + }, "eslint-plugin-standard": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz", diff --git a/package.json b/package.json index 532f9c5f9ce2192a4da2f2bf573ecd7778ce953f..e59898e8df9ea78054045efdb18bdd4a0b4bce92 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,7 @@ "eslint-plugin-promise": "^4.1.1", "eslint-plugin-react": "^7.12.4", "eslint-plugin-react-hooks": "^1.6.0", + "eslint-plugin-sort-class-members": "^1.4.0", "eslint-plugin-standard": "^4.0.0", "fetch-ponyfill": "^6.0.0", "fs-readfile-promise": "^3.0.1", diff --git a/services/amo/amo-base.js b/services/amo/amo-base.js index 3bb863f47004c1ed21085431c7717f12791f8507..394f4014cf078148babc4333dfb3799e2186e183 100644 --- a/services/amo/amo-base.js +++ b/services/amo/amo-base.js @@ -18,16 +18,16 @@ const schema = Joi.object({ }).required() class BaseAmoService extends BaseJsonService { + static get defaultBadgeData() { + return { label: 'mozilla add-on' } + } + async fetch({ addonId }) { return this._requestJson({ schema, url: `https://addons.mozilla.org/api/v3/addons/addon/${addonId}`, }) } - - static get defaultBadgeData() { - return { label: 'mozilla add-on' } - } } module.exports = { BaseAmoService, keywords } diff --git a/services/conda/conda-base.js b/services/conda/conda-base.js index 01812ef460e2586abcb96f8cf2c51031f91e0023..897318af0354ddc375fc718268f4cb25a631af0e 100644 --- a/services/conda/conda-base.js +++ b/services/conda/conda-base.js @@ -19,14 +19,14 @@ const condaSchema = Joi.object({ }).required() module.exports = class BaseCondaService extends BaseJsonService { + static get defaultBadgeData() { + return { label: 'conda' } + } + async fetch({ channel, pkg }) { return this._requestJson({ schema: condaSchema, url: `https://api.anaconda.org/package/${channel}/${pkg}`, }) } - - static get defaultBadgeData() { - return { label: 'conda' } - } } diff --git a/services/powershellgallery/powershellgallery.service.js b/services/powershellgallery/powershellgallery.service.js index e92df73f7dc00da2989e45985a140266d8f7224f..72541c861330626e206bed552bcbe19dbcaac801 100644 --- a/services/powershellgallery/powershellgallery.service.js +++ b/services/powershellgallery/powershellgallery.service.js @@ -33,12 +33,6 @@ class PowershellGalleryPlatformSupport extends BaseXmlService { return 'platform-support' } - static get defaultBadgeData() { - return { - label: 'platform', - } - } - static get route() { return { base: 'powershellgallery/p', @@ -58,6 +52,12 @@ class PowershellGalleryPlatformSupport extends BaseXmlService { ] } + static get defaultBadgeData() { + return { + label: 'platform', + } + } + static render({ platforms }) { return { message: platforms.join(' | '), diff --git a/services/pub/pub.service.js b/services/pub/pub.service.js index 2368c3952f5f644e13bf1f2aeb8b812f0cbd9258..52da8bdfd261efd7686fad1bd583948e10f5c9ca 100644 --- a/services/pub/pub.service.js +++ b/services/pub/pub.service.js @@ -22,10 +22,6 @@ module.exports = class PubVersion extends BaseJsonService { } } - static get defaultBadgeData() { - return { label: 'pub' } - } - static get examples() { return [ { @@ -45,6 +41,10 @@ module.exports = class PubVersion extends BaseJsonService { ] } + static get defaultBadgeData() { + return { label: 'pub' } + } + async fetch({ packageName }) { return this._requestJson({ schema, diff --git a/services/puppetforge/puppetforge-module-version.service.js b/services/puppetforge/puppetforge-module-version.service.js index b083804bf4b65d566acb37bdbe6800a9d1c74354..14e5b90ae5afc027e0c73d583cfa8cf8d209a946 100644 --- a/services/puppetforge/puppetforge-module-version.service.js +++ b/services/puppetforge/puppetforge-module-version.service.js @@ -15,10 +15,6 @@ module.exports = class PuppetforgeModuleVersion extends BasePuppetForgeModulesSe } } - static get defaultBadgeData() { - return { label: 'puppetforge' } - } - static get examples() { return [ { @@ -32,6 +28,10 @@ module.exports = class PuppetforgeModuleVersion extends BasePuppetForgeModulesSe ] } + static get defaultBadgeData() { + return { label: 'puppetforge' } + } + async handle({ user, moduleName }) { const data = await this.fetch({ user, moduleName }) return renderVersionBadge({ version: data.current_release.version }) diff --git a/services/puppetforge/puppetforge-user-module-count.service.js b/services/puppetforge/puppetforge-user-module-count.service.js index c9663ed6e983b33f0f5fe25aa20fc3de850838fb..3fd3f3cec39bacc9784502530cc7a5e6fb9959cb 100644 --- a/services/puppetforge/puppetforge-user-module-count.service.js +++ b/services/puppetforge/puppetforge-user-module-count.service.js @@ -9,10 +9,6 @@ module.exports = class PuppetForgeModuleCountService extends BasePuppetForgeUser return 'other' } - static get defaultBadgeData() { - return { label: 'modules' } - } - static get route() { return { base: 'puppetforge/mc', @@ -32,9 +28,8 @@ module.exports = class PuppetForgeModuleCountService extends BasePuppetForgeUser ] } - async handle({ user }) { - const data = await this.fetch({ user }) - return this.constructor.render({ modules: data.module_count }) + static get defaultBadgeData() { + return { label: 'modules' } } static render({ modules }) { @@ -43,4 +38,9 @@ module.exports = class PuppetForgeModuleCountService extends BasePuppetForgeUser color: floorCountColor(modules, 5, 10, 50), } } + + async handle({ user }) { + const data = await this.fetch({ user }) + return this.constructor.render({ modules: data.module_count }) + } } diff --git a/services/puppetforge/puppetforge-user-release-count.service.js b/services/puppetforge/puppetforge-user-release-count.service.js index 43d14e749540183ce37808876cf806f0bbf4597d..babd938d16118cf61a70864d95bcc3f842f36685 100644 --- a/services/puppetforge/puppetforge-user-release-count.service.js +++ b/services/puppetforge/puppetforge-user-release-count.service.js @@ -9,10 +9,6 @@ module.exports = class PuppetForgeReleaseCountService extends BasePuppetForgeUse return 'other' } - static get defaultBadgeData() { - return { label: 'releases' } - } - static get route() { return { base: 'puppetforge/rc', @@ -32,9 +28,8 @@ module.exports = class PuppetForgeReleaseCountService extends BasePuppetForgeUse ] } - async handle({ user }) { - const data = await this.fetch({ user }) - return this.constructor.render({ releases: data.release_count }) + static get defaultBadgeData() { + return { label: 'releases' } } static render({ releases }) { @@ -43,4 +38,9 @@ module.exports = class PuppetForgeReleaseCountService extends BasePuppetForgeUse color: floorCountColor(releases, 10, 50, 100), } } + + async handle({ user }) { + const data = await this.fetch({ user }) + return this.constructor.render({ releases: data.release_count }) + } } diff --git a/services/pypi/pypi-djversions.service.js b/services/pypi/pypi-djversions.service.js index 77399444b706ca4915fc3ae58c0716accb2c4b76..d2ab1e54d038fd6bb6d7e0080e463082d888767c 100644 --- a/services/pypi/pypi-djversions.service.js +++ b/services/pypi/pypi-djversions.service.js @@ -12,10 +12,6 @@ module.exports = class PypiDjangoVersions extends PypiBase { return this.buildRoute('pypi/djversions') } - static get defaultBadgeData() { - return { label: 'django versions' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiDjangoVersions extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'django versions' } + } + static render({ versions }) { if (versions.length > 0) { return { diff --git a/services/pypi/pypi-downloads.service.js b/services/pypi/pypi-downloads.service.js index 6cd768d69dbbe218e84aa0591ceea3cc31b15f5d..617a62b6c1908a0d9efe2ff98d3aa189c4bc8580 100644 --- a/services/pypi/pypi-downloads.service.js +++ b/services/pypi/pypi-downloads.service.js @@ -34,33 +34,6 @@ const periodMap = { // this badge uses PyPI Stats instead of the PyPI API // so it doesn't extend PypiBase module.exports = class PypiDownloads extends BaseJsonService { - async fetch({ packageName }) { - return this._requestJson({ - url: `https://pypistats.org/api/packages/${packageName.toLowerCase()}/recent`, - schema, - errorMessages: { 404: 'package not found' }, - }) - } - - static render({ period, downloads }) { - return { - message: `${metric(downloads)}${periodMap[period].suffix}`, - color: downloadCount(downloads), - } - } - - async handle({ period, packageName }) { - const json = await this.fetch({ packageName }) - return this.constructor.render({ - period, - downloads: json.data[periodMap[period].api_field], - }) - } - - static get defaultBadgeData() { - return { label: 'downloads' } - } - static get category() { return 'downloads' } @@ -85,4 +58,31 @@ module.exports = class PypiDownloads extends BaseJsonService { }, ] } + + static get defaultBadgeData() { + return { label: 'downloads' } + } + + static render({ period, downloads }) { + return { + message: `${metric(downloads)}${periodMap[period].suffix}`, + color: downloadCount(downloads), + } + } + + async fetch({ packageName }) { + return this._requestJson({ + url: `https://pypistats.org/api/packages/${packageName.toLowerCase()}/recent`, + schema, + errorMessages: { 404: 'package not found' }, + }) + } + + async handle({ period, packageName }) { + const json = await this.fetch({ packageName }) + return this.constructor.render({ + period, + downloads: json.data[periodMap[period].api_field], + }) + } } diff --git a/services/pypi/pypi-format.service.js b/services/pypi/pypi-format.service.js index 2dc514c69772a8d54d79f24ea33339ac5299b069..ea64f04271ddb1add4e8d71310548772a20ab653 100644 --- a/services/pypi/pypi-format.service.js +++ b/services/pypi/pypi-format.service.js @@ -12,10 +12,6 @@ module.exports = class PypiFormat extends PypiBase { return this.buildRoute('pypi/format') } - static get defaultBadgeData() { - return { label: 'format' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiFormat extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'format' } + } + static render({ hasWheel, hasEgg }) { if (hasWheel) { return { diff --git a/services/pypi/pypi-implementation.service.js b/services/pypi/pypi-implementation.service.js index 659a581b0243110b0688e8ea73b491a23ff46d87..1fd33a4d30789f81ab381cf5e5ef40eca7cad31a 100644 --- a/services/pypi/pypi-implementation.service.js +++ b/services/pypi/pypi-implementation.service.js @@ -12,10 +12,6 @@ module.exports = class PypiImplementation extends PypiBase { return this.buildRoute('pypi/implementation') } - static get defaultBadgeData() { - return { label: 'implementation' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiImplementation extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'implementation' } + } + static render({ implementations }) { return { message: implementations.sort().join(' | '), diff --git a/services/pypi/pypi-pyversions.service.js b/services/pypi/pypi-pyversions.service.js index ff559c4b700159ed9eaf6ebfadc13b13378870a9..8a4b4168ac531d760a564653956f31a6ebf73230 100644 --- a/services/pypi/pypi-pyversions.service.js +++ b/services/pypi/pypi-pyversions.service.js @@ -12,10 +12,6 @@ module.exports = class PypiPythonVersions extends PypiBase { return this.buildRoute('pypi/pyversions') } - static get defaultBadgeData() { - return { label: 'python' } - } - static get examples() { return [ { @@ -27,6 +23,10 @@ module.exports = class PypiPythonVersions extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'python' } + } + static render({ versions }) { const versionSet = new Set(versions) // We only show v2 if eg. v2.4 does not appear. diff --git a/services/pypi/pypi-status.service.js b/services/pypi/pypi-status.service.js index 9b1385a2771d7f2264b0b1a6f1d8d9f4224d9a41..0ae484a3681d1c77f3b1e4c53e36f1e27294bf10 100644 --- a/services/pypi/pypi-status.service.js +++ b/services/pypi/pypi-status.service.js @@ -12,10 +12,6 @@ module.exports = class PypiStatus extends PypiBase { return this.buildRoute('pypi/status') } - static get defaultBadgeData() { - return { label: 'status' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiStatus extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'status' } + } + static render({ status = '' }) { status = status.toLowerCase() diff --git a/services/pypi/pypi-version.service.js b/services/pypi/pypi-version.service.js index cee37daafc2e5eeee258121f89cd564cf550fd16..271adfe6ebfb95fc7a10d40d3f48bccaf9f378c8 100644 --- a/services/pypi/pypi-version.service.js +++ b/services/pypi/pypi-version.service.js @@ -12,10 +12,6 @@ module.exports = class PypiVersion extends PypiBase { return this.buildRoute('pypi/v') } - static get defaultBadgeData() { - return { label: 'pypi' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiVersion extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'pypi' } + } + static render({ version }) { return renderVersionBadge({ version }) } diff --git a/services/pypi/pypi-wheel.service.js b/services/pypi/pypi-wheel.service.js index 3c0367cac2a51520a51574a64e459a58bc997068..91a5b448aed29f993fefe38f3ef54746cf8b8ed7 100644 --- a/services/pypi/pypi-wheel.service.js +++ b/services/pypi/pypi-wheel.service.js @@ -12,10 +12,6 @@ module.exports = class PypiWheel extends PypiBase { return this.buildRoute('pypi/wheel') } - static get defaultBadgeData() { - return { label: 'wheel' } - } - static get examples() { return [ { @@ -28,6 +24,10 @@ module.exports = class PypiWheel extends PypiBase { ] } + static get defaultBadgeData() { + return { label: 'wheel' } + } + static render({ hasWheel }) { if (hasWheel) { return { diff --git a/services/redmine/redmine.service.js b/services/redmine/redmine.service.js index 9f7b0974c307ea3f0ad16b2c38b020767771ad69..03645746623d5991abe8b6a1e563508d3bec9585 100644 --- a/services/redmine/redmine.service.js +++ b/services/redmine/redmine.service.js @@ -14,11 +14,6 @@ const schema = Joi.object({ }) class BaseRedminePluginRating extends BaseXmlService { - async fetch({ plugin }) { - const url = `https://www.redmine.org/plugins/${plugin}.xml` - return this._requestXml({ schema, url }) - } - static get category() { return 'rating' } @@ -27,6 +22,11 @@ class BaseRedminePluginRating extends BaseXmlService { throw new Error(`render() function not implemented for ${this.name}`) } + async fetch({ plugin }) { + const url = `https://www.redmine.org/plugins/${plugin}.xml` + return this._requestXml({ schema, url }) + } + async handle({ plugin }) { const data = await this.fetch({ plugin }) const rating = data['redmine-plugin']['ratings-average'] @@ -42,10 +42,6 @@ class RedminePluginRating extends BaseRedminePluginRating { } } - static get defaultBadgeData() { - return { label: 'redmine' } - } - static get examples() { return [ { @@ -56,6 +52,10 @@ class RedminePluginRating extends BaseRedminePluginRating { ] } + static get defaultBadgeData() { + return { label: 'redmine' } + } + static render({ rating }) { return { label: 'rating', diff --git a/services/requires/requires.service.js b/services/requires/requires.service.js index cd04a5032af00a4f76736eb5ab6a93572c06158a..5e8ea1a0947ad4e47d2af6aeeba3bcb8b3d9cdda 100644 --- a/services/requires/requires.service.js +++ b/services/requires/requires.service.js @@ -8,6 +8,10 @@ const statusSchema = Joi.object({ }).required() module.exports = class RequiresIo extends BaseJsonService { + static get category() { + return 'dependencies' + } + static get route() { return { base: 'requires', @@ -15,42 +19,6 @@ module.exports = class RequiresIo extends BaseJsonService { } } - static get defaultBadgeData() { - return { label: 'requirements' } - } - - async handle({ service, user, repo, branch }) { - const { status } = await this.fetch({ service, user, repo, branch }) - return this.constructor.render({ status }) - } - - async fetch({ service, user, repo, branch }) { - const url = `https://requires.io/api/v1/status/${service}/${user}/${repo}` - return this._requestJson({ - url, - schema: statusSchema, - options: { qs: { branch } }, - }) - } - - static render({ status }) { - let message = status - let color = 'lightgrey' - if (status === 'up-to-date') { - message = 'up to date' - color = 'brightgreen' - } else if (status === 'outdated') { - color = 'yellow' - } else if (status === 'insecure') { - color = 'red' - } - return { message, color } - } - - static get category() { - return 'dependencies' - } - static get examples() { return [ { @@ -72,4 +40,36 @@ module.exports = class RequiresIo extends BaseJsonService { }, ] } + + static get defaultBadgeData() { + return { label: 'requirements' } + } + + static render({ status }) { + let message = status + let color = 'lightgrey' + if (status === 'up-to-date') { + message = 'up to date' + color = 'brightgreen' + } else if (status === 'outdated') { + color = 'yellow' + } else if (status === 'insecure') { + color = 'red' + } + return { message, color } + } + + async fetch({ service, user, repo, branch }) { + const url = `https://requires.io/api/v1/status/${service}/${user}/${repo}` + return this._requestJson({ + url, + schema: statusSchema, + options: { qs: { branch } }, + }) + } + + async handle({ service, user, repo, branch }) { + const { status } = await this.fetch({ service, user, repo, branch }) + return this.constructor.render({ status }) + } } diff --git a/services/sonar/sonar-base.js b/services/sonar/sonar-base.js index b25afc2e791469cc64a9a87d7f79d32570bbd876..d7bf177d26f7d441d699f99d6418e3dd822b0a51 100644 --- a/services/sonar/sonar-base.js +++ b/services/sonar/sonar-base.js @@ -40,17 +40,6 @@ const legacyApiSchema = Joi.array() .required() module.exports = class SonarBase extends BaseJsonService { - transform({ json, sonarVersion }) { - const useLegacyApi = isLegacyVersion({ sonarVersion }) - const rawValue = useLegacyApi - ? json[0].msr[0].val - : json.component.measures[0].value - const value = parseInt(rawValue) - - // Most values are numeric, but not all of them. - return { metricValue: value || rawValue } - } - async fetch({ sonarVersion, protocol, host, component, metricName }) { let qs, url const useLegacyApi = isLegacyVersion({ sonarVersion }) @@ -88,4 +77,15 @@ module.exports = class SonarBase extends BaseJsonService { }, }) } + + transform({ json, sonarVersion }) { + const useLegacyApi = isLegacyVersion({ sonarVersion }) + const rawValue = useLegacyApi + ? json[0].msr[0].val + : json.component.measures[0].value + const value = parseInt(rawValue) + + // Most values are numeric, but not all of them. + return { metricValue: value || rawValue } + } } diff --git a/services/stackexchange/stackexchange-monthlyquestions.service.js b/services/stackexchange/stackexchange-monthlyquestions.service.js index 2639319a02806cb1b47af219de5b4a38b5623d66..afe7a7fadbf93cefde08198034b240b36293b899 100644 --- a/services/stackexchange/stackexchange-monthlyquestions.service.js +++ b/services/stackexchange/stackexchange-monthlyquestions.service.js @@ -16,8 +16,11 @@ module.exports = class StackExchangeMonthlyQuestions extends BaseJsonService { return 'chat' } - static get defaultBadgeData() { - return { label: 'stackoverflow' } + static get route() { + return { + base: 'stackexchange', + pattern: ':stackexchangesite/qm/:query', + } } static get examples() { @@ -35,11 +38,8 @@ module.exports = class StackExchangeMonthlyQuestions extends BaseJsonService { ] } - static get route() { - return { - base: 'stackexchange', - pattern: ':stackexchangesite/qm/:query', - } + static get defaultBadgeData() { + return { label: 'stackoverflow' } } static render(props) { diff --git a/services/stackexchange/stackexchange-reputation.service.js b/services/stackexchange/stackexchange-reputation.service.js index 597fd445275882ebf6d22c94a47aef2ef3f67ba2..fd7d6adf57930d8ee21e3b309b0f37905834dd96 100644 --- a/services/stackexchange/stackexchange-reputation.service.js +++ b/services/stackexchange/stackexchange-reputation.service.js @@ -30,10 +30,6 @@ module.exports = class StackExchangeReputation extends BaseJsonService { } } - static get defaultBadgeData() { - return { label: 'stackoverflow' } - } - static get examples() { return [ { @@ -48,6 +44,10 @@ module.exports = class StackExchangeReputation extends BaseJsonService { ] } + static get defaultBadgeData() { + return { label: 'stackoverflow' } + } + static render({ stackexchangesite, numValue }) { const label = `${stackexchangesite} reputation` diff --git a/services/stackexchange/stackexchange-taginfo.service.js b/services/stackexchange/stackexchange-taginfo.service.js index 6281b77966f64c4fca19f1a8c57722d636e51754..254591fab5ee8cdc6c0d566505ecc3b605d874bf 100644 --- a/services/stackexchange/stackexchange-taginfo.service.js +++ b/services/stackexchange/stackexchange-taginfo.service.js @@ -22,8 +22,11 @@ module.exports = class StackExchangeQuestions extends BaseJsonService { return 'chat' } - static get defaultBadgeData() { - return { label: 'stackoverflow' } + static get route() { + return { + base: 'stackexchange', + pattern: ':stackexchangesite/t/:query', + } } static get examples() { @@ -41,11 +44,8 @@ module.exports = class StackExchangeQuestions extends BaseJsonService { ] } - static get route() { - return { - base: 'stackexchange', - pattern: ':stackexchangesite/t/:query', - } + static get defaultBadgeData() { + return { label: 'stackoverflow' } } static render(props) { diff --git a/services/steam/steam-workshop.service.js b/services/steam/steam-workshop.service.js index 608086159f45c2501fc0e3f6b97c52246325d149..360aad3d68494f33ef7cd05d3e81f92c038ce1a4 100644 --- a/services/steam/steam-workshop.service.js +++ b/services/steam/steam-workshop.service.js @@ -121,6 +121,36 @@ const fileFoundOrNotSchema = Joi.alternatives( ) class SteamCollectionSize extends BaseSteamAPI { + static get category() { + return 'other' + } + + static get route() { + return { + base: 'steam/collection-files', + pattern: ':collectionId', + } + } + + static get examples() { + return [ + { + title: 'Steam Collection Files', + namedParams: { collectionId: '180077636' }, + staticPreview: this.render({ size: 32 }), + documentation, + }, + ] + } + + static get defaultBadgeData() { + return { label: 'files' } + } + + static render({ size }) { + return { message: metric(size), color: 'brightgreen' } + } + static get interf() { return 'ISteamRemoteStorage' } @@ -133,10 +163,6 @@ class SteamCollectionSize extends BaseSteamAPI { return '1' } - static render({ size }) { - return { message: metric(size), color: 'brightgreen' } - } - async handle({ collectionId }) { const options = { method: 'POST', @@ -159,32 +185,6 @@ class SteamCollectionSize extends BaseSteamAPI { size: json.response.collectiondetails[0].children.length, }) } - - static get category() { - return 'other' - } - - static get defaultBadgeData() { - return { label: 'files' } - } - - static get route() { - return { - base: 'steam/collection-files', - pattern: ':collectionId', - } - } - - static get examples() { - return [ - { - title: 'Steam Collection Files', - namedParams: { collectionId: '180077636' }, - staticPreview: this.render({ size: 32 }), - documentation, - }, - ] - } } class SteamFileService extends BaseSteamAPI { @@ -200,6 +200,10 @@ class SteamFileService extends BaseSteamAPI { return '1' } + async onRequest({ response }) { + throw new Error(`onRequest() wasn't implemented for ${this.name}`) + } + async handle({ fileId }) { const options = { method: 'POST', @@ -217,29 +221,13 @@ class SteamFileService extends BaseSteamAPI { return this.onRequest({ response: json.response.publishedfiledetails[0] }) } - - async onRequest({ response }) { - throw new Error(`onRequest() wasn't implemented for ${this.name}`) - } } class SteamFileSize extends SteamFileService { - static render({ fileSize }) { - return { message: prettyBytes(fileSize), color: 'brightgreen' } - } - - async onRequest({ response }) { - return this.constructor.render({ fileSize: response.file_size }) - } - static get category() { return 'size' } - static get defaultBadgeData() { - return { label: 'size' } - } - static get route() { return { base: 'steam/size', @@ -257,20 +245,23 @@ class SteamFileSize extends SteamFileService { }, ] } -} -class SteamFileReleaseDate extends SteamFileService { - static render({ releaseDate }) { - return { message: formatDate(releaseDate), color: ageColor(releaseDate) } + static get defaultBadgeData() { + return { label: 'size' } + } + + static render({ fileSize }) { + return { message: prettyBytes(fileSize), color: 'brightgreen' } } async onRequest({ response }) { - const releaseDate = new Date(0).setUTCSeconds(response.time_created) - return this.constructor.render({ releaseDate }) + return this.constructor.render({ fileSize: response.file_size }) } +} - static get defaultBadgeData() { - return { label: 'release date' } +class SteamFileReleaseDate extends SteamFileService { + static get category() { + return 'activity' } static get route() { @@ -293,24 +284,21 @@ class SteamFileReleaseDate extends SteamFileService { ] } - static get category() { - return 'activity' + static get defaultBadgeData() { + return { label: 'release date' } } -} -class SteamFileSubscriptions extends SteamFileService { - static render({ subscriptions }) { - return { message: metric(subscriptions), color: 'brightgreen' } + static render({ releaseDate }) { + return { message: formatDate(releaseDate), color: ageColor(releaseDate) } } async onRequest({ response }) { - return this.constructor.render({ subscriptions: response.subscriptions }) - } - - static get defaultBadgeData() { - return { label: 'subscriptions' } + const releaseDate = new Date(0).setUTCSeconds(response.time_created) + return this.constructor.render({ releaseDate }) } +} +class SteamFileSubscriptions extends SteamFileService { static get category() { return 'rating' } @@ -332,21 +320,21 @@ class SteamFileSubscriptions extends SteamFileService { }, ] } -} -class SteamFileFavorites extends SteamFileService { - static render({ favorites }) { - return { message: metric(favorites), color: 'brightgreen' } + static get defaultBadgeData() { + return { label: 'subscriptions' } } - async onRequest({ response }) { - return this.constructor.render({ favorites: response.favorited }) + static render({ subscriptions }) { + return { message: metric(subscriptions), color: 'brightgreen' } } - static get defaultBadgeData() { - return { label: 'favorites' } + async onRequest({ response }) { + return this.constructor.render({ subscriptions: response.subscriptions }) } +} +class SteamFileFavorites extends SteamFileService { static get category() { return 'rating' } @@ -368,27 +356,25 @@ class SteamFileFavorites extends SteamFileService { }, ] } -} -class SteamFileDownloads extends SteamFileService { - static render({ downloads }) { - return { message: metric(downloads), color: downloadCount(downloads) } + static get defaultBadgeData() { + return { label: 'favorites' } + } + + static render({ favorites }) { + return { message: metric(favorites), color: 'brightgreen' } } async onRequest({ response }) { - return this.constructor.render({ - downloads: response.lifetime_subscriptions, - }) + return this.constructor.render({ favorites: response.favorited }) } +} +class SteamFileDownloads extends SteamFileService { static get category() { return 'downloads' } - static get defaultBadgeData() { - return { label: 'downloads' } - } - static get route() { return { base: 'steam/downloads', @@ -406,19 +392,25 @@ class SteamFileDownloads extends SteamFileService { }, ] } -} -class SteamFileViews extends SteamFileService { - static render({ views }) { - return { message: metric(views), color: 'brightgreen' } + static get defaultBadgeData() { + return { label: 'downloads' } + } + + static render({ downloads }) { + return { message: metric(downloads), color: downloadCount(downloads) } } async onRequest({ response }) { - return this.constructor.render({ views: response.views }) + return this.constructor.render({ + downloads: response.lifetime_subscriptions, + }) } +} - static get defaultBadgeData() { - return { label: 'views' } +class SteamFileViews extends SteamFileService { + static get category() { + return 'other' } static get route() { @@ -439,8 +431,16 @@ class SteamFileViews extends SteamFileService { ] } - static get category() { - return 'other' + static get defaultBadgeData() { + return { label: 'views' } + } + + static render({ views }) { + return { message: metric(views), color: 'brightgreen' } + } + + async onRequest({ response }) { + return this.constructor.render({ views: response.views }) } } diff --git a/services/swagger/swagger.service.js b/services/swagger/swagger.service.js index 904ad537f40898e088399beb9fd91c0ff3e32608..f084048dd46c633df93ddd4bc4bc792163d8c344 100644 --- a/services/swagger/swagger.service.js +++ b/services/swagger/swagger.service.js @@ -15,8 +15,8 @@ const validatorSchema = Joi.object() .required() module.exports = class SwaggerValidatorService extends BaseJsonService { - static render({ message, clr }) { - return { message, color: clr } + static get category() { + return 'other' } static get route() { @@ -26,19 +26,27 @@ module.exports = class SwaggerValidatorService extends BaseJsonService { } } + static get examples() { + return [ + { + title: 'Swagger Validator', + pattern: ':scheme/:url', + staticPreview: this.render({ message: 'valid', clr: 'brightgreen' }), + namedParams: { + scheme: 'https', + url: + 'raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore-expanded.json', + }, + }, + ] + } + static get defaultBadgeData() { return { label: 'swagger' } } - async handle({ scheme, url }) { - const json = await this.fetch({ scheme, urlF: url }) - const valMessages = json.schemaValidationMessages - - if (!valMessages || valMessages.length === 0) { - return this.constructor.render({ message: 'valid', clr: 'brightgreen' }) - } else { - return this.constructor.render({ message: 'invalid', clr: 'red' }) - } + static render({ message, clr }) { + return { message, color: clr } } async fetch({ scheme, urlF }) { @@ -54,22 +62,14 @@ module.exports = class SwaggerValidatorService extends BaseJsonService { }) } - static get category() { - return 'other' - } + async handle({ scheme, url }) { + const json = await this.fetch({ scheme, urlF: url }) + const valMessages = json.schemaValidationMessages - static get examples() { - return [ - { - title: 'Swagger Validator', - pattern: ':scheme/:url', - staticPreview: this.render({ message: 'valid', clr: 'brightgreen' }), - namedParams: { - scheme: 'https', - url: - 'raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore-expanded.json', - }, - }, - ] + if (!valMessages || valMessages.length === 0) { + return this.constructor.render({ message: 'valid', clr: 'brightgreen' }) + } else { + return this.constructor.render({ message: 'invalid', clr: 'red' }) + } } } diff --git a/services/symfony/symfony-insight-base.js b/services/symfony/symfony-insight-base.js index 3ba6b642a226699bfbe6b66ad6d92acd227e073c..3305fced6cc02f91d9f1eef58722bb752ca18033 100644 --- a/services/symfony/symfony-insight-base.js +++ b/services/symfony/symfony-insight-base.js @@ -47,16 +47,16 @@ const gradeColors = { } class SymfonyInsightBase extends BaseXmlService { + static get category() { + return 'analysis' + } + static get defaultBadgeData() { return { label: 'symfony insight', } } - static get category() { - return 'analysis' - } - async fetch({ projectUuid }) { const url = `https://insight.symfony.com/api/projects/${projectUuid}` const options = { diff --git a/services/symfony/symfony-insight-grade.service.js b/services/symfony/symfony-insight-grade.service.js index 92952f804b7af715307ae4a16c9bab3352c47853..b5b211d20a40e0afb2fa307f2713f9fce86d5272 100644 --- a/services/symfony/symfony-insight-grade.service.js +++ b/services/symfony/symfony-insight-grade.service.js @@ -7,24 +7,6 @@ const { } = require('./symfony-insight-base') module.exports = class SymfonyInsightGrade extends SymfonyInsightBase { - static render({ status, grade }) { - const label = 'grade' - if (status !== 'finished' && status !== '') { - return { - label, - message: 'pending', - color: 'lightgrey', - } - } - - const message = grade === 'none' ? 'no medal' : grade - return { - label, - message, - color: gradeColors[grade], - } - } - static get route() { return { base: 'symfony/i/grade', @@ -48,6 +30,24 @@ module.exports = class SymfonyInsightGrade extends SymfonyInsightBase { ] } + static render({ status, grade }) { + const label = 'grade' + if (status !== 'finished' && status !== '') { + return { + label, + message: 'pending', + color: 'lightgrey', + } + } + + const message = grade === 'none' ? 'no medal' : grade + return { + label, + message, + color: gradeColors[grade], + } + } + async handle({ projectUuid }) { const data = await this.fetch({ projectUuid }) const { grade, status } = this.transform({ data }) diff --git a/services/symfony/symfony-insight-stars.service.js b/services/symfony/symfony-insight-stars.service.js index a66eac4ca2523bd064f81039414a36a030bb82b5..38470f455e748b92888f08f2f60b0136e27565e3 100644 --- a/services/symfony/symfony-insight-stars.service.js +++ b/services/symfony/symfony-insight-stars.service.js @@ -16,23 +16,6 @@ const gradeStars = { } module.exports = class SymfonyInsightStars extends SymfonyInsightBase { - static render({ status, grade }) { - const label = 'stars' - if (status !== 'finished' && status !== '') { - return { - label, - message: 'pending', - color: 'lightgrey', - } - } - const numStars = gradeStars[grade] - return { - label, - message: starRating(numStars, 4), - color: gradeColors[grade], - } - } - static get route() { return { base: 'symfony/i/stars', @@ -56,6 +39,23 @@ module.exports = class SymfonyInsightStars extends SymfonyInsightBase { ] } + static render({ status, grade }) { + const label = 'stars' + if (status !== 'finished' && status !== '') { + return { + label, + message: 'pending', + color: 'lightgrey', + } + } + const numStars = gradeStars[grade] + return { + label, + message: starRating(numStars, 4), + color: gradeColors[grade], + } + } + async handle({ projectUuid }) { const data = await this.fetch({ projectUuid }) const { grade, status } = this.transform({ data }) diff --git a/services/symfony/symfony-insight-violations.service.js b/services/symfony/symfony-insight-violations.service.js index bee3720dd7eeef9f1ea67832446a12a058311541..270506502794b8e889e5649816c9851af6a5e437 100644 --- a/services/symfony/symfony-insight-violations.service.js +++ b/services/symfony/symfony-insight-violations.service.js @@ -3,6 +3,29 @@ const { SymfonyInsightBase, keywords } = require('./symfony-insight-base') module.exports = class SymfonyInsightViolations extends SymfonyInsightBase { + static get route() { + return { + base: 'symfony/i/violations', + pattern: ':projectUuid', + } + } + + static get examples() { + return [ + { + title: 'SymfonyInsight Violations', + namedParams: { + projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', + }, + staticPreview: this.render({ + numViolations: 0, + status: 'finished', + }), + keywords, + }, + ] + } + static render({ status, numViolations, @@ -54,29 +77,6 @@ module.exports = class SymfonyInsightViolations extends SymfonyInsightBase { } } - static get route() { - return { - base: 'symfony/i/violations', - pattern: ':projectUuid', - } - } - - static get examples() { - return [ - { - title: 'SymfonyInsight Violations', - namedParams: { - projectUuid: '45afb680-d4e6-4e66-93ea-bcfa79eb8a87', - }, - staticPreview: this.render({ - numViolations: 0, - status: 'finished', - }), - keywords, - }, - ] - } - async handle({ projectUuid }) { const data = await this.fetch({ projectUuid }) const lastAnalysis = this.transform({ data }) diff --git a/services/teamcity/teamcity-build.service.js b/services/teamcity/teamcity-build.service.js index 52f20876501e71ad6c6f790b6069cfc37540213e..f3e7e8fb4a9964b78dee2cf5266ad17ec0c858a9 100644 --- a/services/teamcity/teamcity-build.service.js +++ b/services/teamcity/teamcity-build.service.js @@ -15,31 +15,6 @@ const buildStatusSchema = Joi.object({ }).required() module.exports = class TeamCityBuild extends TeamCityBase { - static render({ status, statusText, useVerbose }) { - if (status === 'SUCCESS') { - return { - message: 'passing', - color: 'brightgreen', - } - } else if (statusText && useVerbose) { - return { - message: statusText.toLowerCase(), - color: 'red', - } - } else { - return { - message: status.toLowerCase(), - color: 'red', - } - } - } - - static get defaultBadgeData() { - return { - label: 'build', - } - } - static get category() { return 'build' } @@ -94,6 +69,31 @@ module.exports = class TeamCityBuild extends TeamCityBase { ] } + static get defaultBadgeData() { + return { + label: 'build', + } + } + + static render({ status, statusText, useVerbose }) { + if (status === 'SUCCESS') { + return { + message: 'passing', + color: 'brightgreen', + } + } else if (statusText && useVerbose) { + return { + message: statusText.toLowerCase(), + color: 'red', + } + } else { + return { + message: status.toLowerCase(), + color: 'red', + } + } + } + async handle({ protocol, hostAndPath, verbosity, buildId }) { // JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-BuildStatusIcon const buildLocator = `buildType:(id:${buildId})` diff --git a/services/teamcity/teamcity-coverage.service.js b/services/teamcity/teamcity-coverage.service.js index f89d1e81339d2f5cfa61e19375dd4d0d030adf83..dc41fea87b8f51f5a9a30d88d40268180841c6b1 100644 --- a/services/teamcity/teamcity-coverage.service.js +++ b/services/teamcity/teamcity-coverage.service.js @@ -17,19 +17,6 @@ const buildStatisticsSchema = Joi.object({ }).required() module.exports = class TeamCityCoverage extends TeamCityBase { - static render({ coverage }) { - return { - message: `${coverage.toFixed(0)}%`, - color: coveragePercentage(coverage), - } - } - - static get defaultBadgeData() { - return { - label: 'coverage', - } - } - static get category() { return 'coverage' } @@ -69,21 +56,17 @@ module.exports = class TeamCityCoverage extends TeamCityBase { ] } - async handle({ protocol, hostAndPath, buildId }) { - // JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-Statistics - const buildLocator = `buildType:(id:${buildId})` - const apiPath = `app/rest/builds/${encodeURIComponent( - buildLocator - )}/statistics` - const data = await this.fetch({ - protocol, - hostAndPath, - apiPath, - schema: buildStatisticsSchema, - }) + static get defaultBadgeData() { + return { + label: 'coverage', + } + } - const { coverage } = this.transform({ data }) - return this.constructor.render({ coverage }) + static render({ coverage }) { + return { + message: `${coverage.toFixed(0)}%`, + color: coveragePercentage(coverage), + } } transform({ data }) { @@ -104,4 +87,21 @@ module.exports = class TeamCityCoverage extends TeamCityBase { throw new InvalidResponse({ prettyMessage: 'no coverage data available' }) } + + async handle({ protocol, hostAndPath, buildId }) { + // JetBrains Docs: https://confluence.jetbrains.com/display/TCD18/REST+API#RESTAPI-Statistics + const buildLocator = `buildType:(id:${buildId})` + const apiPath = `app/rest/builds/${encodeURIComponent( + buildLocator + )}/statistics` + const data = await this.fetch({ + protocol, + hostAndPath, + apiPath, + schema: buildStatisticsSchema, + }) + + const { coverage } = this.transform({ data }) + return this.constructor.render({ coverage }) + } } diff --git a/services/twitter/twitter.service.js b/services/twitter/twitter.service.js index 499891d1164985077095678fe9840a5d846ef3cd..4344036f319fb80d11ffb17a6fe57c8139c01bc7 100644 --- a/services/twitter/twitter.service.js +++ b/services/twitter/twitter.service.js @@ -94,14 +94,6 @@ class TwitterFollow extends BaseJsonService { } } - async fetch({ user }) { - return this._requestJson({ - schema, - url: `http://cdn.syndication.twimg.com/widgets/followbutton/info.json`, - options: { qs: { screen_names: user } }, - }) - } - static render({ user, followers }) { return { label: `follow @${user}`, @@ -114,6 +106,14 @@ class TwitterFollow extends BaseJsonService { } } + async fetch({ user }) { + return this._requestJson({ + schema, + url: `http://cdn.syndication.twimg.com/widgets/followbutton/info.json`, + options: { qs: { screen_names: user } }, + }) + } + async handle({ user }) { const data = await this.fetch({ user }) if (data.length === 0) { diff --git a/services/uptimerobot/uptimerobot-ratio.service.js b/services/uptimerobot/uptimerobot-ratio.service.js index 06d4e897a2ef60dc9f783e3c3fbd3b8a33d78137..377a00343a3e9a93b13fb40a31f386b64b81b4bd 100644 --- a/services/uptimerobot/uptimerobot-ratio.service.js +++ b/services/uptimerobot/uptimerobot-ratio.service.js @@ -6,12 +6,6 @@ const UptimeRobotBase = require('./uptimerobot-base') const ratioColor = colorScale([10, 30, 50, 70]) module.exports = class UptimeRobotRatio extends UptimeRobotBase { - static get defaultBadgeData() { - return { - label: 'uptime', - } - } - static get route() { return { base: 'uptimerobot/ratio', @@ -40,6 +34,12 @@ module.exports = class UptimeRobotRatio extends UptimeRobotBase { ] } + static get defaultBadgeData() { + return { + label: 'uptime', + } + } + static render({ ratio }) { return { message: `${ratio}%`, diff --git a/services/uptimerobot/uptimerobot-status.service.js b/services/uptimerobot/uptimerobot-status.service.js index 9af5698faccb9046766fc9e1e8c903e0859beedb..92921c8769a6153bf35ef38ff6b6425f24db154c 100644 --- a/services/uptimerobot/uptimerobot-status.service.js +++ b/services/uptimerobot/uptimerobot-status.service.js @@ -3,12 +3,6 @@ const UptimeRobotBase = require('./uptimerobot-base') module.exports = class UptimeRobotStatus extends UptimeRobotBase { - static get defaultBadgeData() { - return { - label: 'status', - } - } - static get route() { return { base: 'uptimerobot/status', @@ -28,6 +22,12 @@ module.exports = class UptimeRobotStatus extends UptimeRobotBase { ] } + static get defaultBadgeData() { + return { + label: 'status', + } + } + static render({ status }) { switch (status) { case 0: diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js index 877c139c078b77c8aecd1986310794a535b469ca..76cdf576d7a99c80ea6c675c24cee74a18e2e35f 100644 --- a/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js +++ b/services/visual-studio-marketplace/visual-studio-marketplace-azure-devops-installs.service.js @@ -26,19 +26,6 @@ module.exports = class VisualStudioMarketplaceAzureDevOpsInstalls extends Visual } } - static get defaultBadgeData() { - return { - label: 'installs', - } - } - - static render({ count }) { - return { - message: metric(count), - color: downloadCount(count), - } - } - static get examples() { return [ { @@ -54,6 +41,19 @@ module.exports = class VisualStudioMarketplaceAzureDevOpsInstalls extends Visual ] } + static get defaultBadgeData() { + return { + label: 'installs', + } + } + + static render({ count }) { + return { + message: metric(count), + color: downloadCount(count), + } + } + async handle({ measure, extensionId }) { const json = await this.fetch({ extensionId }) const { statistics } = this.transformStatistics({ json }) diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js index 2317d1894abdfd43e1a534a8c47a9859e4c6a7dd..921670c8404fbdf4b138a3669ef90369804d1ca9 100644 --- a/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js +++ b/services/visual-studio-marketplace/visual-studio-marketplace-downloads.service.js @@ -26,16 +26,6 @@ module.exports = class VisualStudioMarketplaceDownloads extends VisualStudioMark } } - static render({ measure, count }) { - const label = measure === 'd' ? 'downloads' : 'installs' - - return { - label, - message: metric(count), - color: downloadCount(count), - } - } - static get examples() { return [ { @@ -57,6 +47,16 @@ module.exports = class VisualStudioMarketplaceDownloads extends VisualStudioMark ] } + static render({ measure, count }) { + const label = measure === 'd' ? 'downloads' : 'installs' + + return { + label, + message: metric(count), + color: downloadCount(count), + } + } + async handle({ measure, extensionId }) { const json = await this.fetch({ extensionId }) const { statistics } = this.transformStatistics({ json }) diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js index fe748619c2bcb5eb2d7e349cc065266eb7373a79..8e457c7a36dc91f52810cdf806b13d36593caa04 100644 --- a/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js +++ b/services/visual-studio-marketplace/visual-studio-marketplace-rating.service.js @@ -17,23 +17,6 @@ module.exports = class VisualStudioMarketplaceRating extends VisualStudioMarketp } } - static get defaultBadgeData() { - return { - label: 'rating', - } - } - - static render({ format, averageRating, ratingCount }) { - const message = - format === 'r' - ? `${averageRating.toFixed(1)}/5 (${ratingCount})` - : starRating(averageRating) - return { - message, - color: floorCount(averageRating, 2, 3, 4), - } - } - static get examples() { return [ { @@ -60,6 +43,23 @@ module.exports = class VisualStudioMarketplaceRating extends VisualStudioMarketp ] } + static get defaultBadgeData() { + return { + label: 'rating', + } + } + + static render({ format, averageRating, ratingCount }) { + const message = + format === 'r' + ? `${averageRating.toFixed(1)}/5 (${ratingCount})` + : starRating(averageRating) + return { + message, + color: floorCount(averageRating, 2, 3, 4), + } + } + async handle({ format, extensionId }) { const json = await this.fetch({ extensionId }) const { statistics } = this.transformStatistics({ json }) diff --git a/services/visual-studio-marketplace/visual-studio-marketplace-version.service.js b/services/visual-studio-marketplace/visual-studio-marketplace-version.service.js index 7431fbdcd4da44cd8864db326eb1ec0ad3d1314a..a42fdc52f995b66b6c5c6fa2aa0196dd2ef151c4 100644 --- a/services/visual-studio-marketplace/visual-studio-marketplace-version.service.js +++ b/services/visual-studio-marketplace/visual-studio-marketplace-version.service.js @@ -15,16 +15,6 @@ module.exports = class VisualStudioMarketplaceVersion extends VisualStudioMarket } } - static get defaultBadgeData() { - return { - label: 'version', - } - } - - static render({ version }) { - return renderVersionBadge({ version }) - } - static get examples() { return [ { @@ -37,6 +27,16 @@ module.exports = class VisualStudioMarketplaceVersion extends VisualStudioMarket ] } + static get defaultBadgeData() { + return { + label: 'version', + } + } + + static render({ version }) { + return renderVersionBadge({ version }) + } + transform({ json }) { const { extension } = this.transformExtension({ json }) const version = extension.versions[0].version diff --git a/services/waffle/waffle-label.service.js b/services/waffle/waffle-label.service.js index 095e75fbf3b6cf8dec90c1d24f095dea0f71a477..653ec6ad0cf4f871efe4dc40986e0f0939dc1cb6 100644 --- a/services/waffle/waffle-label.service.js +++ b/services/waffle/waffle-label.service.js @@ -23,33 +23,15 @@ module.exports = class WaffleLabel extends BaseJsonService { return 'issue-tracking' } - static get route() { - return { - base: 'waffle/label', - pattern: ':user/:repo/:label', - } - } - - static get defaultBadgeData() { - return { label: 'waffle' } - } - static get isDeprecated() { const now = new Date() return now.getTime() >= new Date('2019-05-16') } - static render({ label, color, count }) { - if (count === 'absent') { - return { message: count } - } - if (count === 0 || !color) { - color = '78bdf2' - } + static get route() { return { - label, - message: metric(count), - color, + base: 'waffle/label', + pattern: ':user/:repo/:label', } } @@ -71,6 +53,24 @@ module.exports = class WaffleLabel extends BaseJsonService { ] } + 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, + } + } + async fetch({ user, repo }) { const url = `https://api.waffle.io/${user}/${repo}/columns` return this._requestJson({ diff --git a/services/wercker/wercker.service.js b/services/wercker/wercker.service.js index 24a32c83f0ba9729165bf6134d821c93b76f5fbf..267887e92c62555d1bdb7bd8bf0f662389a57fd7 100644 --- a/services/wercker/wercker.service.js +++ b/services/wercker/wercker.service.js @@ -15,53 +15,6 @@ const werckerSchema = Joi.array() .required() module.exports = class Wercker extends BaseJsonService { - static getBaseUrl({ projectId, applicationName }) { - if (applicationName) { - return `https://app.wercker.com/api/v3/applications/${applicationName}/builds` - } else { - return `https://app.wercker.com/api/v3/runs?applicationId=${projectId}` - } - } - - async fetch({ baseUrl, branch }) { - return this._requestJson({ - schema: werckerSchema, - url: baseUrl, - options: { - qs: { - branch, - limit: 1, - }, - }, - errorMessages: { - 401: 'private application not supported', - 404: 'application not found', - }, - }) - } - - static render({ result }) { - return renderBuildStatusBadge({ status: result }) - } - - async handle({ projectId, applicationName, branch }) { - const json = await this.fetch({ - baseUrl: this.constructor.getBaseUrl({ - projectId, - applicationName, - }), - branch, - }) - if (json.length === 0) { - return this.constructor.render({ - result: 'not built', - }) - } - const { result } = json[0] - return this.constructor.render({ result }) - } - - // Metadata static get category() { return 'build' } @@ -113,4 +66,50 @@ module.exports = class Wercker extends BaseJsonService { }, ] } + + static render({ result }) { + return renderBuildStatusBadge({ status: result }) + } + + static getBaseUrl({ projectId, applicationName }) { + if (applicationName) { + return `https://app.wercker.com/api/v3/applications/${applicationName}/builds` + } else { + return `https://app.wercker.com/api/v3/runs?applicationId=${projectId}` + } + } + + async fetch({ baseUrl, branch }) { + return this._requestJson({ + schema: werckerSchema, + url: baseUrl, + options: { + qs: { + branch, + limit: 1, + }, + }, + errorMessages: { + 401: 'private application not supported', + 404: 'application not found', + }, + }) + } + + async handle({ projectId, applicationName, branch }) { + const json = await this.fetch({ + baseUrl: this.constructor.getBaseUrl({ + projectId, + applicationName, + }), + branch, + }) + if (json.length === 0) { + return this.constructor.render({ + result: 'not built', + }) + } + const { result } = json[0] + return this.constructor.render({ result }) + } } diff --git a/services/wheelmap/wheelmap.service.js b/services/wheelmap/wheelmap.service.js index f463c893553e93e9580262f43468bfdd5ef5e030..f6861b3ad9365717bc2f680451ee32da6056ee23 100644 --- a/services/wheelmap/wheelmap.service.js +++ b/services/wheelmap/wheelmap.service.js @@ -11,6 +11,43 @@ const wheelmapSchema = Joi.object({ }).required() module.exports = class Wheelmap extends BaseJsonService { + static get category() { + return 'other' + } + + static get route() { + return { + base: 'wheelmap/a', + pattern: ':nodeId(-?[0-9]+)', + } + } + + static get examples() { + return [ + { + title: 'Wheelmap', + namedParams: { nodeId: '26699541' }, + staticPreview: this.render({ accessibility: 'yes' }), + }, + ] + } + + static get defaultBadgeData() { + return { label: 'accessibility' } + } + + static render({ accessibility }) { + let color + if (accessibility === 'yes') { + color = 'brightgreen' + } else if (accessibility === 'limited') { + color = 'yellow' + } else if (accessibility === 'no') { + color = 'red' + } + return { message: accessibility, color } + } + async fetch({ nodeId }) { let options if (serverSecrets.wheelmap_token) { @@ -32,46 +69,9 @@ module.exports = class Wheelmap extends BaseJsonService { }) } - static render({ accessibility }) { - let color - if (accessibility === 'yes') { - color = 'brightgreen' - } else if (accessibility === 'limited') { - color = 'yellow' - } else if (accessibility === 'no') { - color = 'red' - } - return { message: accessibility, color } - } - async handle({ nodeId }) { const json = await this.fetch({ nodeId }) const accessibility = json.node.wheelchair return this.constructor.render({ accessibility }) } - - static get defaultBadgeData() { - return { label: 'accessibility' } - } - - static get category() { - return 'other' - } - - static get route() { - return { - base: 'wheelmap/a', - pattern: ':nodeId(-?[0-9]+)', - } - } - - static get examples() { - return [ - { - title: 'Wheelmap', - namedParams: { nodeId: '26699541' }, - staticPreview: this.render({ accessibility: 'yes' }), - }, - ] - } } diff --git a/services/wordpress/wordpress-downloads.service.js b/services/wordpress/wordpress-downloads.service.js index 48655b735887ba960c08a3af598993e9bf6e6329..5a5aeeb830209dcb67b46cec51887773fefdecfd 100644 --- a/services/wordpress/wordpress-downloads.service.js +++ b/services/wordpress/wordpress-downloads.service.js @@ -29,29 +29,10 @@ function DownloadsForExtensionType(extensionType) { return `Wordpress${capt}Downloads` } - static render({ downloads }) { - return { - message: metric(downloads), - color: downloadCount(downloads), - } - } - - async handle({ slug }) { - const { downloaded: downloads } = await this.fetch({ - extensionType, - slug, - }) - return this.constructor.render({ downloads }) - } - static get category() { return 'downloads' } - static get defaultBadgeData() { - return { label: 'downloads' } - } - static get route() { return { base: `wordpress/${extensionType}/dt`, @@ -68,38 +49,38 @@ function DownloadsForExtensionType(extensionType) { }, ] } - } -} - -function InstallsForExtensionType(extensionType) { - const { capt, exampleSlug } = extensionData[extensionType] - - return class WordpressInstalls extends BaseWordpress { - static get name() { - return `Wordpress${capt}Installs` - } - static get category() { - return 'downloads' + static get defaultBadgeData() { + return { label: 'downloads' } } - static render({ installCount }) { + static render({ downloads }) { return { - message: metric(installCount), - color: downloadCount(installCount), + message: metric(downloads), + color: downloadCount(downloads), } } async handle({ slug }) { - const { active_installs: installCount } = await this.fetch({ + const { downloaded: downloads } = await this.fetch({ extensionType, slug, }) - return this.constructor.render({ installCount }) + return this.constructor.render({ downloads }) } + } +} - static get defaultBadgeData() { - return { label: 'active installs' } +function InstallsForExtensionType(extensionType) { + const { capt, exampleSlug } = extensionData[extensionType] + + return class WordpressInstalls extends BaseWordpress { + static get name() { + return `Wordpress${capt}Installs` + } + + static get category() { + return 'downloads' } static get route() { @@ -118,6 +99,25 @@ function InstallsForExtensionType(extensionType) { }, ] } + + static get defaultBadgeData() { + return { label: 'active installs' } + } + + static render({ installCount }) { + return { + message: metric(installCount), + color: downloadCount(installCount), + } + } + + async handle({ slug }) { + const { active_installs: installCount } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ installCount }) + } } } @@ -158,10 +158,6 @@ function DownloadsForInterval(interval) { return 'downloads' } - static get defaultBadgeData() { - return { label: 'downloads' } - } - static get route() { return { base, @@ -179,6 +175,10 @@ function DownloadsForInterval(interval) { ] } + static get defaultBadgeData() { + return { label: 'downloads' } + } + static render({ downloads }) { return { message: `${metric(downloads)}${messageSuffix}`, diff --git a/services/wordpress/wordpress-platform.service.js b/services/wordpress/wordpress-platform.service.js index 66cc59ba6680c9ba53af11c2fe065cc9642073b1..c7f16d2b3bc0ef8644f1f36dbf27ff6e4287ed77 100644 --- a/services/wordpress/wordpress-platform.service.js +++ b/services/wordpress/wordpress-platform.service.js @@ -52,10 +52,6 @@ class WordpressPluginTestedVersion extends BaseWordpress { return 'platform-support' } - static get defaultBadgeData() { - return { label: 'wordpress' } - } - static get route() { return { base: `wordpress/plugin/tested`, @@ -75,6 +71,10 @@ class WordpressPluginTestedVersion extends BaseWordpress { ] } + static get defaultBadgeData() { + return { label: 'wordpress' } + } + static renderStaticPreview({ testedVersion }) { // Since this badge has an async `render()` function, but `get examples()` has to // be synchronous, this method exists. It should return the same value as the