Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service definition export format #2397

Merged
merged 8 commits into from
Dec 2, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ typings/
.next
badge-examples.json
supported-features.json
service-definitions.yml
chris48s marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
13 changes: 13 additions & 0 deletions scripts/export-service-definitions-cli.js
Original file line number Diff line number Diff line change
@@ -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 }))
60 changes: 54 additions & 6 deletions services/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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')

class BaseService {
constructor({ sendAndCacheRequest }, { handleInternalErrors }) {
Expand Down Expand Up @@ -69,6 +72,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
Expand All @@ -94,17 +101,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.
Expand Down Expand Up @@ -163,7 +173,7 @@ class BaseService {
staticExample,
documentation,
keywords,
} = validateExample(example, index, this)
} = oldValidateExample(example, index, this)

const stringified = queryString.stringify(query)
const suffix = stringified ? `?${stringified}` : ''
Expand Down Expand Up @@ -203,6 +213,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(
Expand Down
80 changes: 80 additions & 0 deletions services/base.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,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(),
Expand Down
1 change: 1 addition & 0 deletions services/bithound/bithound.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/)?(?:.+?)',
Expand Down
32 changes: 32 additions & 0 deletions services/categories.js
Original file line number Diff line number Diff line change
@@ -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' },
chris48s marked this conversation as resolved.
Show resolved Hide resolved
]

const isValidCategory = Joi.equal(categories.map(({ id }) => id)).required()

function assertValidCategory(category, message = undefined) {
Joi.assert(category, isValidCategory, message)
}

module.exports = {
categories,
isValidCategory,
assertValidCategory,
}
1 change: 1 addition & 0 deletions services/cauditor/cauditor.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)/(?:[^/]+)/(?:[^/]+)/(?:.+)',
Expand Down
4 changes: 4 additions & 0 deletions services/deprecated-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ function deprecatedService({ url, label, category, examples = [] }) {
return url
}

static get isDeprecated() {
return true
}

static get defaultBadgeData() {
return { label }
}
Expand Down
1 change: 1 addition & 0 deletions services/dotnetstatus/dotnetstatus.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '(?:.+)',
Expand Down
1 change: 1 addition & 0 deletions services/gemnasium/gemnasium.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const deprecatedService = require('../deprecated-service')

module.exports = deprecatedService({
category: 'dependencies',
url: {
base: 'gemnasium',
format: '(?:.+)',
Expand Down
1 change: 1 addition & 0 deletions services/gratipay/gratipay.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const deprecatedService = require('../deprecated-service')

module.exports = deprecatedService({
category: 'funding',
url: {
format: '(?:gittip|gratipay(?:/user|/team|/project)?)/(?:.*)',
},
Expand Down
1 change: 1 addition & 0 deletions services/imagelayers/imagelayers.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '(?:.+)',
Expand Down
16 changes: 16 additions & 0 deletions services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
}
Expand All @@ -57,4 +72,5 @@ module.exports = {
InvalidService,
loadServiceClasses,
loadTesters,
collectDefinitions,
}
10 changes: 9 additions & 1 deletion services/index.spec.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -54,4 +58,8 @@ describe('loadServiceClasses function', function() {
])
).to.have.length(5)
})

it('can collect the service definitions', function() {
expect(() => collectDefinitions()).not.to.throw()
})
})
1 change: 1 addition & 0 deletions services/issuestats/issuestats.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const deprecatedService = require('../deprecated-service')

module.exports = deprecatedService({
category: 'issue-tracking',
url: {
base: 'issuestats',
format: '(?:[^/]+)(?:/long)?/(?:[^/]+)/(?:.+)',
Expand Down
1 change: 1 addition & 0 deletions services/libscore/libscore.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const deprecatedService = require('../deprecated-service')

module.exports = deprecatedService({
category: 'rating',
url: {
base: 'libscore',
format: 's/(?:.+)',
Expand Down
1 change: 1 addition & 0 deletions services/magnumci/magnumci.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '(?:[^/]+)(?:/(?:.+))?',
Expand Down
Loading