From 74799e4f83563876fcfc30ff9c7a4480047e8b79 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Mon, 26 Nov 2018 14:46:29 -0500 Subject: [PATCH 1/5] Fix one busted static example --- services/aur/aur.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/aur/aur.service.js b/services/aur/aur.service.js index 65938bd6bd6f0..3e239a9e5762e 100644 --- a/services/aur/aur.service.js +++ b/services/aur/aur.service.js @@ -118,7 +118,7 @@ class AurVotes extends BaseAurService { title: `AUR votes`, pattern: ':package', exampleUrl: 'yaourt', - staticExample: this.render({ license: '3029' }), + staticExample: this.render({ votes: 3029 }), }, ] } From 2c82e2938a07a2da425fee122ccacd5ce36880c8 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Mon, 26 Nov 2018 14:46:55 -0500 Subject: [PATCH 2/5] Declare a category for all deprecated badges --- services/bithound/bithound.service.js | 1 + services/cauditor/cauditor.service.js | 1 + services/dotnetstatus/dotnetstatus.service.js | 1 + services/gemnasium/gemnasium.service.js | 1 + services/gratipay/gratipay.service.js | 1 + services/imagelayers/imagelayers.service.js | 1 + services/issuestats/issuestats.service.js | 1 + services/libscore/libscore.service.js | 1 + services/magnumci/magnumci.service.js | 1 + services/snap-ci/snap-ci.service.js | 1 + services/versioneye/versioneye.service.js | 1 + 11 files changed, 11 insertions(+) diff --git a/services/bithound/bithound.service.js b/services/bithound/bithound.service.js index 551da49f69116..7c090dee8d23f 100644 --- a/services/bithound/bithound.service.js +++ b/services/bithound/bithound.service.js @@ -4,6 +4,7 @@ const deprecatedService = require('../deprecated-service') // bitHound integration - deprecated as of July 2018 module.exports = deprecatedService({ + category: 'dependencies', url: { base: 'bithound', format: '(?:code/|dependencies/|devDependencies/)?(?:.+?)', diff --git a/services/cauditor/cauditor.service.js b/services/cauditor/cauditor.service.js index 5f9aeddd546c0..b7eb3ba1d72ff 100644 --- a/services/cauditor/cauditor.service.js +++ b/services/cauditor/cauditor.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'other', url: { base: 'cauditor', format: '(?:mi|ccn|npath|hi|i|ca|ce|dit)/(?:[^/]+)/(?:[^/]+)/(?:.+)', diff --git a/services/dotnetstatus/dotnetstatus.service.js b/services/dotnetstatus/dotnetstatus.service.js index d66230ddf0072..6e31da10349b1 100644 --- a/services/dotnetstatus/dotnetstatus.service.js +++ b/services/dotnetstatus/dotnetstatus.service.js @@ -4,6 +4,7 @@ const deprecatedService = require('../deprecated-service') // dotnet-status integration - deprecated as of April 2018. module.exports = deprecatedService({ + category: 'dependencies', url: { base: 'dotnetstatus', format: '(?:.+)', diff --git a/services/gemnasium/gemnasium.service.js b/services/gemnasium/gemnasium.service.js index 13aa221276b32..fb69a3649c4dc 100644 --- a/services/gemnasium/gemnasium.service.js +++ b/services/gemnasium/gemnasium.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'dependencies', url: { base: 'gemnasium', format: '(?:.+)', diff --git a/services/gratipay/gratipay.service.js b/services/gratipay/gratipay.service.js index 92a0d106c3446..bcb9c2f5a2fe2 100644 --- a/services/gratipay/gratipay.service.js +++ b/services/gratipay/gratipay.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'funding', url: { format: '(?:gittip|gratipay(?:/user|/team|/project)?)/(?:.*)', }, diff --git a/services/imagelayers/imagelayers.service.js b/services/imagelayers/imagelayers.service.js index 1b49ae97ecfd3..9c2632d6d06a7 100644 --- a/services/imagelayers/imagelayers.service.js +++ b/services/imagelayers/imagelayers.service.js @@ -4,6 +4,7 @@ const deprecatedService = require('../deprecated-service') // image layers integration - deprecated as of November 2018. module.exports = deprecatedService({ + category: 'size', url: { base: 'imagelayers', format: '(?:.+)', diff --git a/services/issuestats/issuestats.service.js b/services/issuestats/issuestats.service.js index 4f7a1432e723a..15ce2523700fb 100644 --- a/services/issuestats/issuestats.service.js +++ b/services/issuestats/issuestats.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'issue-tracking', url: { base: 'issuestats', format: '(?:[^/]+)(?:/long)?/(?:[^/]+)/(?:.+)', diff --git a/services/libscore/libscore.service.js b/services/libscore/libscore.service.js index 1eea25edd5211..ea95977d158fc 100644 --- a/services/libscore/libscore.service.js +++ b/services/libscore/libscore.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'rating', url: { base: 'libscore', format: 's/(?:.+)', diff --git a/services/magnumci/magnumci.service.js b/services/magnumci/magnumci.service.js index e30433448ffd8..d3f0ea84a99b0 100644 --- a/services/magnumci/magnumci.service.js +++ b/services/magnumci/magnumci.service.js @@ -4,6 +4,7 @@ const deprecatedService = require('../deprecated-service') // Magnum CI integration - deprecated as of July 2018 module.exports = deprecatedService({ + category: 'build', url: { base: 'magnumci/ci', format: '(?:[^/]+)(?:/(?:.+))?', diff --git a/services/snap-ci/snap-ci.service.js b/services/snap-ci/snap-ci.service.js index 8cd61a97e4b74..2988b3cbca274 100644 --- a/services/snap-ci/snap-ci.service.js +++ b/services/snap-ci/snap-ci.service.js @@ -3,6 +3,7 @@ const deprecatedService = require('../deprecated-service') module.exports = deprecatedService({ + category: 'build', url: { format: 'snap(?:-ci?)/(?:[^/]+/[^/]+)(?:/(?:.+))', }, diff --git a/services/versioneye/versioneye.service.js b/services/versioneye/versioneye.service.js index 9e00dd0a5a108..cc8656970d647 100644 --- a/services/versioneye/versioneye.service.js +++ b/services/versioneye/versioneye.service.js @@ -4,6 +4,7 @@ const deprecatedService = require('../deprecated-service') // VersionEye integration - deprecated as of August 2018. module.exports = deprecatedService({ + category: 'downloads', url: { base: 'versioneye', format: 'd/(?:.+)', From d9ecea36864ccc11171ee772445f76c7b5d93cef Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Mon, 26 Nov 2018 15:00:32 -0500 Subject: [PATCH 3/5] Service definition export format --- .gitignore | 1 + package.json | 5 +- scripts/export-service-definitions-cli.js | 13 ++ services/base.js | 60 ++++++++- services/base.spec.js | 80 ++++++++++++ services/categories.js | 32 +++++ services/deprecated-service.js | 4 + services/index.js | 16 +++ services/index.spec.js | 10 +- services/service-definitions.js | 95 +++++++++++++++ services/transform-example.js | 141 ++++++++++++++++++++++ services/transform-example.spec.js | 53 ++++++++ services/validate-example.js | 4 + 13 files changed, 505 insertions(+), 9 deletions(-) create mode 100644 scripts/export-service-definitions-cli.js create mode 100644 services/categories.js create mode 100644 services/service-definitions.js create mode 100644 services/transform-example.js create mode 100644 services/transform-example.spec.js diff --git a/.gitignore b/.gitignore index d472d817804f6..44e115f0798da 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,4 @@ typings/ .next badge-examples.json supported-features.json +service-definitions.yml diff --git a/package.json b/package.json index 239d7135e42a3..5ec7c96990333 100644 --- a/package.json +++ b/package.json @@ -85,13 +85,14 @@ "postinstall": "npm run depcheck", "prebuild": "npm run depcheck", "features": "node scripts/export-supported-features-cli.js > supported-features.json", + "defs": "node scripts/export-service-definitions-cli.js > service-definitions.yml", "examples": "node scripts/export-badge-examples-cli.js > badge-examples.json", - "build": "npm run examples && npm run features && next build && next export -o build/", + "build": "npm run examples && npm run defs && npm run features && next build && next export -o build/", "heroku-postbuild": "npm run build", "analyze": "ANALYZE=true LONG_CACHE=false BASE_URL=https://img.shields.io npm run build", "start:server": "HANDLE_INTERNAL_ERRORS=false RATE_LIMIT=false node server 8080 ::", "now-start": "node server", - "prestart": "npm run depcheck && npm run examples && npm run features", + "prestart": "npm run depcheck && npm run examples && npm run defs && npm run features", "start": "concurrently --names server,frontend \"ALLOWED_ORIGIN=http://localhost:3000 npm run start:server\" \"BASE_URL=http://[::]:8080 next dev\"", "refactoring-report": "node scripts/refactoring-cli.js" }, diff --git a/scripts/export-service-definitions-cli.js b/scripts/export-service-definitions-cli.js new file mode 100644 index 0000000000000..7d33f91225b7a --- /dev/null +++ b/scripts/export-service-definitions-cli.js @@ -0,0 +1,13 @@ +'use strict' + +const yaml = require('js-yaml') + +const { collectDefinitions } = require('../services') + +const definitions = collectDefinitions() + +// Omit undefined +// https://github.com/nodeca/js-yaml/issues/356#issuecomment-312430599 +const cleaned = JSON.parse(JSON.stringify(definitions)) + +process.stdout.write(yaml.safeDump(cleaned, { flowLevel: 5 })) diff --git a/services/base.js b/services/base.js index f1279c06b948b..0a902591d5382 100644 --- a/services/base.js +++ b/services/base.js @@ -21,7 +21,10 @@ const { } = require('../lib/badge-data') const { staticBadgeUrl } = require('../lib/make-badge-url') const trace = require('./trace') -const validateExample = require('./validate-example') +const oldValidateExample = require('./validate-example') +const { validateExample, transformExample } = require('./transform-example') +const { assertValidCategory } = require('./categories') +const { assertValidServiceDefinition } = require('./service-definitions') function coalesce(...candidates) { return candidates.find(c => c !== undefined) @@ -72,6 +75,10 @@ class BaseService { throw new Error(`Route not defined for ${this.name}`) } + static get isDeprecated() { + return false + } + /** * Default data for the badge. Can include things such as default logo, color, * etc. These defaults will be used if the value is not explicitly overridden @@ -97,17 +104,20 @@ class BaseService { * is to use the service class name, which probably is not what you want. * namedParams: An object containing the values of named parameters to * substitute into the compiled route pattern. - * query: An object containing query parameters to include in the example URLs. + * queryParams: An object containing query parameters to include in the + * example URLs. + * query: Deprecated. An alias for `queryParams`. * pattern: The route pattern to compile. Defaults to `this.route.pattern`. * urlPattern: Deprecated. An alias for `pattern`. - * staticExample: A rendered badge of the sort returned by `handle()` or + * staticPreview: A rendered badge of the sort returned by `handle()` or * `render()`: an object containing `message` and optional `label` and * `color`. This is usually generated by invoking `this.render()` with some * explicit props. + * staticExample: Deprecated. An alias for `staticPreview`. * previewUrl: Deprecated. An explicit example which is rendered as part of * the badge listing. - * exampleUrl: Deprecated. An explicit example which will be displayed to - * the user, but not rendered. + * exampleUrl: Deprecated. An explicit example which will _not_ be rendered. + * Only the URL itself is shown to the user. * keywords: Additional keywords, other than words in the title. This helps * users locate relevant badges. * documentation: An HTML string that is included in the badge popup. @@ -166,7 +176,7 @@ class BaseService { staticExample, documentation, keywords, - } = validateExample(example, index, this) + } = oldValidateExample(example, index, this) const stringified = queryString.stringify(query) const suffix = stringified ? `?${stringified}` : '' @@ -206,6 +216,44 @@ class BaseService { }) } + static validateDefinition() { + assertValidCategory(this.category, `Category for ${this.name}`) + + this.examples.forEach((example, index) => + validateExample(example, index, this) + ) + } + + static getDefinition() { + const { category, name, isDeprecated } = this + + let format, pattern, queryParams + try { + ;({ format, pattern, query: queryParams = [] } = this.route) + } catch (e) { + // Legacy services do not have a route. + } + + const examples = this.examples.map((example, index) => + transformExample(example, index, this) + ) + + let route + if (pattern) { + route = { pattern, queryParams } + } else if (format) { + route = { format, queryParams } + } else { + route = undefined + } + + const result = { category, name, isDeprecated, route, examples } + + assertValidServiceDefinition(result, `getDefinition() for ${this.name}`) + + return result + } + static get _regexFromPath() { const { pattern } = this.route const fullPattern = `${this._makeFullUrl( diff --git a/services/base.spec.js b/services/base.spec.js index 3dbaf79835660..19d6b759a34a8 100644 --- a/services/base.spec.js +++ b/services/base.spec.js @@ -524,6 +524,86 @@ describe('BaseService', function() { }) }) + describe('getDefinition', function() { + it('returns the expected result', function() { + const { + examples: [first, second, third, fourth, fifth, sixth], + } = DummyService.getDefinition() + expect(first).to.deep.equal({ + title: 'DummyService', + example: { + path: '/foo/World', + queryParams: {}, + }, + preview: { + path: '/foo/World', + queryParams: {}, + }, + keywords: [], + documentation: undefined, + }) + expect(second).to.deep.equal({ + title: 'DummyService', + example: { + path: '/foo/World', + queryParams: { queryParamA: '!!!' }, + }, + preview: { + path: '/foo/World', + queryParams: { queryParamA: '!!!' }, + }, + keywords: [], + documentation: undefined, + }) + const expectedDefinition = { + title: 'DummyService', + example: { + path: '/foo/World', + queryParams: {}, + }, + preview: { + label: 'cat', + message: 'Hello namedParamA: foo with queryParamA: bar', + color: 'lightgrey', + }, + keywords: ['hello'], + documentation: undefined, + } + expect(third).to.deep.equal(expectedDefinition) + expect(fourth).to.deep.equal(expectedDefinition) + expect(fifth).to.deep.equal({ + title: 'DummyService', + example: { + pattern: '/foo/:world', + namedParams: { world: 'World' }, + queryParams: {}, + }, + preview: { + label: 'cat', + message: 'Hello namedParamA: foo with queryParamA: bar', + color: 'lightgrey', + }, + keywords: ['hello'], + documentation: undefined, + }) + expect(sixth).to.deep.equal({ + title: 'DummyService', + example: { + pattern: '/foo/:world', + namedParams: { world: 'World' }, + queryParams: { queryParamA: '!!!' }, + }, + preview: { + color: 'lightgrey', + label: 'cat', + message: 'Hello namedParamA: foo with queryParamA: bar', + }, + keywords: ['hello'], + documentation: undefined, + }) + }) + }) + describe('validate', function() { const dummySchema = Joi.object({ requiredString: Joi.string().required(), diff --git a/services/categories.js b/services/categories.js new file mode 100644 index 0000000000000..dcfd96a8fbac1 --- /dev/null +++ b/services/categories.js @@ -0,0 +1,32 @@ +'use strict' + +const Joi = require('joi') + +const categories = [ + { id: 'build', name: 'Build' }, + { id: 'chat', name: 'Chat' }, + { id: 'dependencies', name: 'Dependencies' }, + { id: 'size', name: 'Size' }, + { id: 'downloads', name: 'Downloads' }, + { id: 'funding', name: 'Funding' }, + { id: 'issue-tracking', name: 'Issue Tracking' }, + { id: 'license', name: 'License' }, + { id: 'rating', name: 'Rating' }, + { id: 'social', name: 'Social' }, + { id: 'version', name: 'Version' }, + { id: 'platform-support', name: 'Platform & Version Support' }, + { id: 'monitoring', name: 'Monitoring' }, + { id: 'other', name: 'Other' }, +] + +const isValidCategory = Joi.equal(categories.map(({ id }) => id)).required() + +function assertValidCategory(category, message = undefined) { + Joi.assert(category, isValidCategory, message) +} + +module.exports = { + categories, + isValidCategory, + assertValidCategory, +} diff --git a/services/deprecated-service.js b/services/deprecated-service.js index f10311229f2ef..66c2aed3e671f 100644 --- a/services/deprecated-service.js +++ b/services/deprecated-service.js @@ -14,6 +14,10 @@ function deprecatedService({ url, label, category, examples = [] }) { return url } + static get isDeprecated() { + return true + } + static get defaultBadgeData() { return { label } } diff --git a/services/index.js b/services/index.js index 49bb9ec623b99..a3223fb741143 100644 --- a/services/index.js +++ b/services/index.js @@ -2,6 +2,8 @@ const glob = require('glob') const BaseService = require('./base') +const { categories } = require('./categories') +const { assertValidServiceDefinitionExport } = require('./service-definitions') class InvalidService extends Error { constructor(message) { @@ -49,6 +51,19 @@ function loadServiceClasses(servicePaths) { return serviceClasses } +function collectDefinitions() { + const services = loadServiceClasses() + // flatMap. + .map(ServiceClass => ServiceClass.getDefinition()) + .reduce((accum, these) => accum.concat(these), []) + + const result = { schemaVersion: '0', categories, services } + + assertValidServiceDefinitionExport(result) + + return result +} + function loadTesters() { return glob.sync(`${__dirname}/**/*.tester.js`).map(path => require(path)) } @@ -57,4 +72,5 @@ module.exports = { InvalidService, loadServiceClasses, loadTesters, + collectDefinitions, } diff --git a/services/index.spec.js b/services/index.spec.js index dcd612abc9225..a36bd04c927b0 100644 --- a/services/index.spec.js +++ b/services/index.spec.js @@ -1,7 +1,11 @@ 'use strict' const { expect } = require('chai') -const { loadServiceClasses, InvalidService } = require('./index') +const { + loadServiceClasses, + InvalidService, + collectDefinitions, +} = require('./index') describe('loadServiceClasses function', function() { it('throws if module exports empty', function() { @@ -54,4 +58,8 @@ describe('loadServiceClasses function', function() { ]) ).to.have.length(5) }) + + it('can collect the service definitions', function() { + expect(() => collectDefinitions()).not.to.throw() + }) }) diff --git a/services/service-definitions.js b/services/service-definitions.js new file mode 100644 index 0000000000000..4a9cf42e0d6ea --- /dev/null +++ b/services/service-definitions.js @@ -0,0 +1,95 @@ +'use strict' + +const Joi = require('joi') + +const arrayOfStrings = Joi.array() + .items(Joi.string()) + .allow([]) + .required() + +const objectOfKeyValues = Joi.object() + .pattern(/./, Joi.string().allow(null)) + .required() + +const staticBadgeContent = Joi.object({ + label: Joi.string(), + message: Joi.string().required(), + color: Joi.string().required(), +}) + +const serviceDefinition = Joi.object({ + category: Joi.string().required(), + name: Joi.string().required(), + isDeprecated: Joi.boolean().required(), + route: Joi.alternatives().try( + Joi.object({ + pattern: Joi.string().required(), + queryParams: arrayOfStrings, + }), + Joi.object({ + format: Joi.string().required(), + queryParams: arrayOfStrings, + }) + ), + examples: Joi.array() + .items( + Joi.object({ + title: Joi.string().required(), + example: Joi.alternatives() + .try( + Joi.object({ + pattern: Joi.string(), + namedParams: objectOfKeyValues, + queryParams: objectOfKeyValues, + }), + Joi.object({ + path: Joi.string().required(), // URL convertible. + queryParams: objectOfKeyValues, + }) + ) + .required(), + preview: Joi.alternatives() + .try( + staticBadgeContent, + Joi.object({ + path: Joi.string().required(), // URL convertible. + queryParams: objectOfKeyValues, + }) + ) + .required(), + keywords: arrayOfStrings, + documentation: Joi.string(), // Valid HTML. + }) + ) + .default([]), +}).required() + +function assertValidServiceDefinition(example, message = undefined) { + Joi.assert(example, serviceDefinition, message) +} + +const serviceDefinitionExport = Joi.object({ + schemaVersion: Joi.equal('0').required(), + categories: Joi.array() + .items( + Joi.object({ + id: Joi.string().required(), + name: Joi.string().required(), + }) + ) + .required(), + services: Joi.array() + .items(serviceDefinition) + .required(), +}).required() + +function assertValidServiceDefinitionExport(examples, message = undefined) { + Joi.assert(examples, serviceDefinitionExport, message) +} + +module.exports = { + serviceDefinition, + assertValidServiceDefinition, + serviceDefinitionExport, + assertValidServiceDefinitionExport, +} diff --git a/services/transform-example.js b/services/transform-example.js new file mode 100644 index 0000000000000..645a5006b1968 --- /dev/null +++ b/services/transform-example.js @@ -0,0 +1,141 @@ +'use strict' + +const Joi = require('joi') + +const optionalObjectOfKeyValues = Joi.object().pattern( + /./, + Joi.string().allow(null) +) + +const optionalServiceData = Joi.object({ + label: Joi.string(), + message: Joi.alternatives() + .try( + Joi.string() + .allow('') + .required(), + Joi.number() + ) + .required(), + color: Joi.string(), +}) + +const schema = Joi.object({ + // This should be: + // title: Joi.string().required(), + title: Joi.string(), + namedParams: optionalObjectOfKeyValues, + queryParams: optionalObjectOfKeyValues.default({}), + pattern: Joi.string(), + staticPreview: optionalServiceData, + previewUrl: Joi.string(), + exampleUrl: Joi.string(), + keywords: Joi.array() + .items(Joi.string()) + .default([]), + documentation: Joi.string(), // Valid HTML. +}) + .rename('query', 'queryParams', { ignoreUndefined: true }) + .rename('staticExample', 'staticPreview', { ignoreUndefined: true }) + .rename('urlPattern', 'pattern', { ignoreUndefined: true }) + .required() + +function validateExample(example, index, ServiceClass) { + const result = Joi.attempt( + example, + schema, + `Example for ${ServiceClass.name} at index ${index}` + ) + + const { namedParams, pattern, staticPreview, previewUrl, exampleUrl } = result + + if (staticPreview) { + if (!pattern && !ServiceClass.route.pattern) { + throw new Error( + `Static preview for ${ + ServiceClass.name + } at index ${index} does not declare a pattern` + ) + } + if (namedParams && exampleUrl) { + throw new Error( + `Static preview for ${ + ServiceClass.name + } at index ${index} declares both namedParams and exampleUrl` + ) + } else if (!namedParams && !exampleUrl) { + throw new Error( + `Static preview for ${ + ServiceClass.name + } at index ${index} does not declare namedParams nor exampleUrl` + ) + } + if (previewUrl) { + throw new Error( + `Static preview for ${ + ServiceClass.name + } at index ${index} also declares a dynamic previewUrl, which is not allowed` + ) + } + } else if (!previewUrl) { + throw Error( + `Example for ${ + ServiceClass.name + } at index ${index} is missing required previewUrl or staticPreview` + ) + } + + return result +} + +function transformExample(inExample, index, ServiceClass) { + const { + // We should get rid of this transform, since the class name is never what + // we want to see. + title = ServiceClass.name, + namedParams, + queryParams, + pattern, + staticPreview, + previewUrl, + exampleUrl, + keywords, + documentation, + } = validateExample(inExample, index, ServiceClass) + + let example + if (namedParams) { + example = { + pattern: ServiceClass._makeFullUrl(pattern), + namedParams, + queryParams, + } + } else { + example = { + path: ServiceClass._makeFullUrl(exampleUrl || previewUrl), + queryParams, + } + } + + let preview + if (staticPreview) { + const badgeData = ServiceClass._makeBadgeData({}, staticPreview) + preview = { + label: badgeData.text[0], + message: `${badgeData.text[1]}`, + color: badgeData.colorscheme || badgeData.colorB, + } + } else { + preview = { + path: ServiceClass._makeFullUrl(previewUrl), + queryParams, + } + } + + return { title, example, preview, keywords, documentation } +} + +module.exports = { + validateExample, + transformExample, +} diff --git a/services/transform-example.spec.js b/services/transform-example.spec.js new file mode 100644 index 0000000000000..e2648c1ad111b --- /dev/null +++ b/services/transform-example.spec.js @@ -0,0 +1,53 @@ +'use strict' + +const { expect } = require('chai') +const { validateExample } = require('./transform-example') + +describe('validateExample function', function() { + it('passes valid examples', function() { + const validExamples = [ + { + staticExample: { message: '123' }, + pattern: 'dt/:package', + exampleUrl: 'dt/mypackage', + }, + { + staticExample: { message: '123' }, + pattern: 'dt/:package', + namedParams: { package: 'mypackage' }, + }, + { previewUrl: 'dt/mypackage' }, + ] + + validExamples.forEach(example => { + expect(() => + validateExample(example, 0, { route: {}, name: 'mockService' }) + ).not.to.throw(Error) + }) + }) + + it('rejects invalid examples', function() { + const invalidExamples = [ + {}, + { staticExample: { message: '123' } }, + { + staticExample: { message: '123' }, + pattern: 'dt/:package', + namedParams: { package: 'mypackage' }, + exampleUrl: 'dt/mypackage', + }, + { staticExample: { message: '123' }, pattern: 'dt/:package' }, + { + staticExample: { message: '123' }, + pattern: 'dt/:package', + previewUrl: 'dt/mypackage', + }, + ] + + invalidExamples.forEach(example => { + expect(() => + validateExample(example, 0, { route: {}, name: 'mockService' }) + ).to.throw(Error) + }) + }) +}) diff --git a/services/validate-example.js b/services/validate-example.js index d2a6860f3b4ba..14e28b93b43ff 100644 --- a/services/validate-example.js +++ b/services/validate-example.js @@ -4,12 +4,14 @@ module.exports = function validateExample( { title, query, + queryParams, namedParams, exampleUrl, previewUrl, pattern, urlPattern, staticExample, + staticPreview, documentation, keywords, }, @@ -17,6 +19,8 @@ module.exports = function validateExample( ServiceClass ) { pattern = pattern || urlPattern || ServiceClass.route.pattern + staticExample = staticExample || staticPreview + query = query || queryParams if (staticExample) { if (!pattern) { From 1056f669cab6143bf697f8a39e3c434928560c4d Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Sat, 1 Dec 2018 15:56:58 -0500 Subject: [PATCH 4/5] Clean diff --- services/aur/aur.service.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/aur/aur.service.js b/services/aur/aur.service.js index cf5c5b8174546..210af6db64c9d 100644 --- a/services/aur/aur.service.js +++ b/services/aur/aur.service.js @@ -114,9 +114,8 @@ class AurVotes extends BaseAurService { return [ { title: 'AUR votes', - exampleUrl: 'yaourt', namedParams: { packageName: 'yaourt' }, - staticExample: this.render({ votes: 3029 }), + staticExample: this.render({ votes: '3029' }), }, ] } From 3e92daa3f2c81d691908e5d1cf7503043b963705 Mon Sep 17 00:00:00 2001 From: Paul Melnikow Date: Sat, 1 Dec 2018 16:12:29 -0500 Subject: [PATCH 5/5] Fix travis examples --- services/travis/travis-build.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/travis/travis-build.service.js b/services/travis/travis-build.service.js index 6cd06b8b34b33..78dec2ff9dc80 100644 --- a/services/travis/travis-build.service.js +++ b/services/travis/travis-build.service.js @@ -51,7 +51,7 @@ module.exports = class TravisBuild extends LegacyService { ] } - static staticExample() { + static get staticExample() { return { message: 'passing', color: 'brightgreen' } }