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

Extract examples from new-style services #1582

Merged
merged 7 commits into from
Mar 30, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -89,3 +89,4 @@ typings/
# Temporary build artifacts.
/build
.next
badge-examples.json
44 changes: 36 additions & 8 deletions lib/all-badge-examples.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const { loadServiceClasses } = require('../services');

const visualStudioTeamServicesDoc = `
<p>
To obtain your own badge, you will first need to enable badges for your
Expand Down Expand Up @@ -144,14 +146,6 @@ const allBadgeExamples = [
],
exampleUri: '/teamcity/http/teamcity.jetbrains.com/e/bt345.svg'
},
{
title: 'AppVeyor',
previewUri: '/appveyor/ci/gruntjs/grunt.svg'
},
{
title: 'AppVeyor branch',
previewUri: '/appveyor/ci/gruntjs/grunt/master.svg'
},
{
title: 'AppVeyor tests',
previewUri: '/appveyor/tests/NZSmartie/coap-net-iu0to.svg'
Expand Down Expand Up @@ -2260,4 +2254,38 @@ const allBadgeExamples = [
}
];

function prepareExample({ title, previewUri, exampleUri, documentation }, ServiceClass) {
if (! previewUri) {
throw Error(`Example for ${ServiceClass.name} is missing required previewUri`);
}

return {
title: title ? `${ServiceClass.name} ${title}` : ServiceClass.name,
previewUri: `${previewUri}.svg`,
exampleUri: exampleUri ? `${exampleUri}.svg` : undefined,
documentation,
};
}

function getCategory(wantedCategory) {
return allBadgeExamples.find(thisCat => thisCat.category.id === wantedCategory);
}

function loadExamples() {
loadServiceClasses().forEach(ServiceClass => {
const { category: wantedCategory, examples: theseExamples } = ServiceClass;

const category = getCategory(wantedCategory);
if (category === undefined) {
throw Error(`Unknown category ${wantedCategory} referenced in ${ServiceClass.name}`);
}

const prepared = theseExamples.map(
inExample => prepareExample(inExample, ServiceClass));
category.examples = category.examples.concat(prepared);
});
}
loadExamples();

module.exports = allBadgeExamples;
module.exports.getCategory = getCategory;
29 changes: 29 additions & 0 deletions lib/all-badge-examples.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const { expect } = require('chai');

const allBadgeExamples = require('./all-badge-examples');

describe('The badge examples', function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think its worth having another test here which just iterates over all examples (rather than just the appveyor ones) and runs prepareExample() on each one. It doesn't have to assert anything other than an error wasn't thrown. That way we'll get a failure on the server tests if an invalid example (without a previewUri) is added in a PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's a good idea, though I think this is already happening when this file is required, on account of the call to loadExamples().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one step ahead of me :)

it('should include AppVeyor, which is added automatically', function () {
const { examples } = allBadgeExamples.getCategory('build');

const appVeyorBuildExamples = examples.filter(ex => ex.title.includes('AppVeyor'))
.filter(ex => ! ex.title.includes('tests'));

expect(appVeyorBuildExamples).to.deep.equal([
{
title: 'AppVeyor',
previewUri: '/appveyor/ci/gruntjs/grunt.svg',
exampleUri: undefined,
documentation: undefined,
},
{
title: 'AppVeyor branch',
previewUri: '/appveyor/ci/gruntjs/grunt/master.svg',
exampleUri: undefined,
documentation: undefined,
},
]);
});
});
5 changes: 5 additions & 0 deletions lib/export-badge-examples-cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

const allBadgeExamples = require('./all-badge-examples');

process.stdout.write(JSON.stringify(allBadgeExamples));
4 changes: 2 additions & 2 deletions lib/service-test-runner/runner.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const glob = require('glob');
const { loadTesters } = require('../../services');

/**
* Load a collection of ServiceTester objects and register them with Mocha.
Expand All @@ -16,7 +16,7 @@ class Runner {
* Prepare the runner by loading up all the ServiceTester objects.
*/
prepare () {
this.testers = glob.sync(`${__dirname}/../../services/**/*.tester.js`).map(name => require(name));
this.testers = loadTesters();
this.testers.forEach(tester => {
tester.beforeEach = () => { this.beforeEach(); };
});
Expand Down
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ module.exports = {
}));
}

config.module.loaders = (config.module.loaders || []).concat({
test: /\.json$/,
loader: 'json-loader',
});

return config;
},
exportPathMap: () => ({
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@
"depcheck": "check-node-version --node \">= 8.0\"",
"postinstall": "npm run depcheck",
"prebuild": "npm run depcheck",
"build": "next build && next export -o build/",
"examples": "node lib/export-badge-examples-cli.js > badge-examples.json",
"build": "npm run examples && 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": "RATE_LIMIT=false node server 8080 ::",
"now-start": "node server",
"prestart": "npm run depcheck",
"prestart": "npm run depcheck && npm run examples",
"start": "concurrently --names server,frontend \"ALLOWED_ORIGIN=http://localhost:3000 npm run start:server\" \"BASE_URL=http://[::]:8080 next dev\""
},
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BadgeExamples } from '../frontend/components/badge-examples';
import MarkupModal from '../frontend/components/markup-modal';
import Usage from '../frontend/components/usage';
import Footer from '../frontend/components/footer';
import badgeExampleData from '../lib/all-badge-examples';
import badgeExampleData from '../badge-examples.json';
import { prepareExamples, predicateFromQuery } from '../frontend/lib/prepare-examples';

const baseUri = process.env.BASE_URL;
Expand Down
12 changes: 3 additions & 9 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ const dom = require('xmldom').DOMParser;
const jp = require('jsonpath');
const path = require('path');
const prettyBytes = require('pretty-bytes');
const glob = require('glob');
const queryString = require('query-string');
const semver = require('semver');
const xml2js = require('xml2js');
const xpath = require('xpath');

const { loadServiceClasses } = require('./services');
const { isDeprecated, getDeprecatedBadge } = require('./lib/deprecation-helpers');
const { checkErrorResponse } = require('./lib/error-helper');
const analytics = require('./lib/analytics');
Expand Down Expand Up @@ -196,14 +196,8 @@ camp.notfound(/.*/, function(query, match, end, request) {

// Vendors.

// Match modules with the same name as their containing directory.
// e.g. services/appveyor/appveyor.js
const serviceRegex = /\/services\/(.*)\/\1\.js$/;
// New-style services
glob.sync(`${__dirname}/services/**/*.js`)
.filter(path => serviceRegex.test(path))
.map(path => require(path))
.forEach(serviceClass => serviceClass.register(camp, cache));
loadServiceClasses().forEach(
serviceClass => serviceClass.register(camp, cache));

// JIRA issue integration
camp.route(/^\/jira\/issue\/(http(?:s)?)\/(.+)\/([^/]+)\.(svg|png|gif|jpg|json)$/,
Expand Down
8 changes: 4 additions & 4 deletions services/appveyor/appveyor.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ module.exports = class AppVeyor extends BaseService {
};
}

static getExamples() {
static get examples() {
return [
{
uri: '/appveyor/ci/gruntjs/grunt',
previewUri: '/appveyor/ci/gruntjs/grunt',
},
{
name: 'Branch',
uri: '/appveyor/ci/gruntjs/grunt/master',
title: 'branch',
previewUri: '/appveyor/ci/gruntjs/grunt/master',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor duplication, but given we define a base URI pattern in the service class I wonder if there is a way we could define the examples here as gruntjs/grunt/master or { 'repo': 'gruntjs/grunt', 'branch': 'master' } without needing to repeat /appveyor/ci in each example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that makes a lot of sense!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed an update. Could you take a look?

It's cool that you brought up { 'repo': 'gruntjs/grunt', 'branch': 'master' }. I've started some experimentation with path-to-regexp which has a compile function that will help with this. I didn't quite get it working but my progress was promising!

},
];
}
Expand Down
2 changes: 1 addition & 1 deletion services/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module.exports = class BaseService {
* specified in `uri`, and can be used to demonstrate how to use badges for
* this service.
*/
static getExamples() {
static get examples() {
return [];
}

Expand Down
24 changes: 24 additions & 0 deletions services/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const glob = require('glob');

// Match modules with the same name as their containing directory.
// e.g. services/appveyor/appveyor.js
const serviceRegex = /\/services\/(.*)\/\1\.js$/;

function loadServiceClasses() {
// New-style services
return glob.sync(`${__dirname}/**/*.js`)
.filter(path => serviceRegex.test(path))
.map(path => require(path))
}

function loadTesters() {
return glob.sync(`${__dirname}/**/*.tester.js`)
.map(name => require(name));
}

module.exports = {
loadServiceClasses,
loadTesters,
};