From 366e50fadbb0c98a848414cfe400576838982b0d Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Sun, 25 Feb 2018 00:32:03 -0600 Subject: [PATCH 1/4] new badge-json service route: /^\/json\/(https?.+)\.(svg|png|gif|jpg|json)$/ added documentation to usage.js (with json-badge-maker) tweaked usage.js oh-so-slightly to better separate "static", "dynamic", and "json" added "style" parameter to parameter list & moved rendered styles from above the list to below removed the ginormous margin-left: 25% style from ul -> now applies to ul.options (was only one UL in in documentation) renamed duplicate "miscellaneous" id to "miscellaneous2" --- frontend/components/json-badge-maker.js | 36 ++++++++++ frontend/components/usage.js | 94 ++++++++++++++++++++++--- frontend/lib/badge-url.js | 5 ++ lib/all-badge-examples.js | 4 +- server.js | 67 +++++++++++++++++- service-tests/badge-json.js | 54 ++++++++++++++ static/main.css | 5 +- 7 files changed, 253 insertions(+), 12 deletions(-) create mode 100644 frontend/components/json-badge-maker.js create mode 100644 service-tests/badge-json.js diff --git a/frontend/components/json-badge-maker.js b/frontend/components/json-badge-maker.js new file mode 100644 index 0000000000000..242b8133f87f0 --- /dev/null +++ b/frontend/components/json-badge-maker.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { jsonBadgeUrl } from '../lib/badge-url'; + +export default class JsonBadgeMaker extends React.Component { + static propTypes = { + baseUri: PropTypes.string, + }; + + state = { + url: '' + }; + + handleSubmit (e) { + e.preventDefault(); + + const { baseUri } = this.props; + const { url } = this.state; + const badgeUri = jsonBadgeUrl(baseUri || window.location.href, url); + + document.location = badgeUri; + } + + render() { + return ( +
this.handleSubmit(e)}> + this.setState({ url: event.target.value })} + placeholder="url" /> {} + +
+ ); + } +} diff --git a/frontend/components/usage.js b/frontend/components/usage.js index 773ce3b383a19..454152338ca95 100644 --- a/frontend/components/usage.js +++ b/frontend/components/usage.js @@ -2,6 +2,7 @@ import { Fragment, default as React } from 'react'; import PropTypes from 'prop-types'; import StaticBadgeMaker from './static-badge-maker'; import DynamicBadgeMaker from './dynamic-badge-maker'; +import JsonBadgeMaker from './json-badge-maker'; import { staticBadgeUrl } from '../lib/badge-url'; import { advertisedStyles } from '../../lib/supported-features'; @@ -114,27 +115,86 @@ export default class Usage extends React.PureComponent { +

Where color is one of these named colors, or hex (xxxxxx) +

{ this.renderColorExamples() } -

Dynamic

+
- +

Badge JSON

+

- /badge/dynamic/<TYPE>.svg?uri=<URI>&label=<LABEL>&query=<$.DATA.SUBDATA>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX> + + {baseUri}/json/<URL>.svg +
+ (<URL> must be url-encoded)

-
+

Your service/website can speak badge by serving badge-json

-

Styles

+

badge-json is a simple object with the following properties/values.
+ All Values are optional except for label and value +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Properties specified in JSON (* = required)
label *left-hand-side text
value *right-hand-side text
valueClassone of "error", "notice", "success", "info", or "default"
isSocial(boolean) default = false. If true, will get "social" style by default
logo + one of: +
    +
  • named logo
  • +
  • data-uri: data:image/png;base64,…
  • +
+
logoWidthSet the horizontal space to give to the logo
colorAbackground color of the left part (overrides valueClass color scheme)
colorBbackground color of the right part (overrides valueClass color scheme)
+ +
+ +

Dynamic

+ +

- The following styles are available (flat is the default as of Feb 1st 2015): + /badge/dynamic/<TYPE>.svg?uri=<URI>&label=<LABEL>&query=<$.DATA.SUBDATA>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

- { this.renderStyleExamples() } + +

Parameters

- Here are a few other parameters you can use: (connecting several with "&" is possible) + Optional parameters you can use: (connecting several with "&" is possible)

@@ -198,9 +258,27 @@ export default class Usage extends React.PureComponent { + + + +
Set the HTTP cache lifetime in secs
+ ?style=flat + + Specify the badge style.
+ One of : "plastic", "flat", "flat-square", "for-the-badge", or "social" +
+

Styles

+ +

+ The following styles are available (flat is the default as of Feb 1st 2015): +

+ { this.renderStyleExamples() } + +
+

We support .svg, .json, .png and a few others, but use them responsibly. diff --git a/frontend/lib/badge-url.js b/frontend/lib/badge-url.js index ce5506bacc106..091bd30fb2296 100644 --- a/frontend/lib/badge-url.js +++ b/frontend/lib/badge-url.js @@ -45,3 +45,8 @@ export function dynamicJsonBadgeUrl(baseUrl, label, jsonUrl, query, options = {} return resolveBadgeUrl('/badge/dynamic/json.svg', baseUrl, outOptions); } + +export function jsonBadgeUrl(baseUrl, url) { + const path = encodeURIComponent(url); + return resolveUrl(`/json/${path}.svg`, baseUrl, {}); +} diff --git a/lib/all-badge-examples.js b/lib/all-badge-examples.js index edac6b008eb35..fe4312281a5a8 100644 --- a/lib/all-badge-examples.js +++ b/lib/all-badge-examples.js @@ -49,7 +49,7 @@ const websiteDoc = `

[OPTIONS] can be: -

    +
    • Nothing:  …/website/… @@ -2141,7 +2141,7 @@ const allBadgeExamples = [ }, { category: { - id: 'miscellaneous', + id: 'miscellaneous2', name: 'Longer Miscellaneous' }, examples: [ diff --git a/server.js b/server.js index 85b9d25f85ae2..1cdf7a7147019 100644 --- a/server.js +++ b/server.js @@ -7545,6 +7545,71 @@ camp.route(/^\/maven-metadata\/v\/(https?)\/(.+\.xml)\.(svg|png|gif|jpg|json)$/, }); })); +// badge-json url +camp.route(/^\/json\/(https?.+)\.(svg|png|gif|jpg|json)$/, +cache(function(query, match, sendBadge, request) { + const urlEncoded = match[1]; + const format = match[2]; + const defaultLabel = 'badge json'; + let badgeData = getBadgeData(defaultLabel, {}); + try { + const url = encodeURI(decodeURIComponent(urlEncoded)); + request(url, function dealWithData(err, res, buffer) { + if (checkErrorResponse(badgeData, err, res, 'url not found')) { + sendBadge(format, badgeData); + return; + } + try { + let data = JSON.parse(buffer); + if (!data.label) { + // label should be non-empty + throw {name:'message', message:'invalid badge-json'}; + } + if (!data.isSocial && !data.value) { + // non-empty value required when !isSocial + throw {name:'message', message:'invalid badge-json'}; + } + if (data.isSocial) { + data.style = "social"; + } + // defined query vals override json vals + for (let prop in query) { + if (query[prop] !== undefined) { + data[prop] = query[prop]; + } + } + badgeData = getBadgeData(defaultLabel, data); + badgeData.text[1] = data.value; + if (badgeData.colorB === undefined && data.valueClass) { + switch (data.valueClass) { + case "error" : + badgeData.colorscheme = "red"; + break; + case "notice" : + badgeData.colorscheme = "orange"; + break; + case "success" : + badgeData.colorscheme = "brightgreen"; + break; + case "info" : + badgeData.colorscheme = "blue"; + break; + } + } + sendBadge(format, badgeData); + } catch(e) { + badgeData.text[1] = e.name === 'message' + ? e.message + : 'invalid'; + sendBadge(format, badgeData); + } + }); // end request + } catch(e) { + badgeData.text[1] = 'invalid'; + sendBadge(format, badgeData); + } +})); + // User defined sources - JSON response camp.route(/^\/badge\/dynamic\/(json)\.(svg|png|gif|jpg|json)$/, cache({ @@ -7971,7 +8036,7 @@ camp.route(/^\/vaadin-directory\/(star|status|rating|rc|rating-count|v|version|r })); -// Any badge. +// "static" badge. camp.route(/^\/(:|badge\/)(([^-]|--)*?)-(([^-]|--)*)-(([^-]|--)+)\.(svg|png|gif|jpg)$/, function(data, match, end, ask) { var subject = escapeFormat(match[2]); diff --git a/service-tests/badge-json.js b/service-tests/badge-json.js new file mode 100644 index 0000000000000..c42038bb5b837 --- /dev/null +++ b/service-tests/badge-json.js @@ -0,0 +1,54 @@ +'use strict'; + +const Joi = require('joi'); +const ServiceTester = require('./runner/service-tester'); +const { invalidJSON } = require('./helpers/response-fixtures'); + +const t = new ServiceTester({ id: 'badge-json', title: 'badge json', pathPrefix: '/json' }); + +module.exports = t; + +t.create('invalid urlencoding') + .get('/http%3A%2F%test.com%2Fbadge.json.json') + .expectJSON({name: 'badge json', value: 'invalid'}); + +t.create('connection error') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') + .networkOff() + .expectJSON({name: 'badge json', value: 'inaccessible'}); + +t.create('unexpected response') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(invalidJSON) + ) + .expectJSON({name: 'badge json', value: 'invalid'}); + +t.create('invalid badge json') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(function() { + return [ + 200, + '{}', + { 'Content-Type': 'application/json' } + ]; + }) + ) + .expectJSON({name: 'badge json', value: 'invalid badge-json'}); + +t.create('label & value') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(function() { + return [ + 200, + '{"label":"test","value":"success"}', + { 'Content-Type': 'application/json' } + ]; + }) + ) + .expectJSON({name: 'test', value: 'success'}); diff --git a/static/main.css b/static/main.css index 87d9256e8873a..2efd35425b24a 100644 --- a/static/main.css +++ b/static/main.css @@ -22,6 +22,9 @@ code, input.short { width: 5em; } +input.medium { + width: 15em; +} input { text-align: center; @@ -43,7 +46,7 @@ hr { border-width: 1px 0 0 0; } -ul { +ul.options { text-align: left; margin-left: 25%; } From e4633d32d6a45d70f2066a3a8236addef6c12070 Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Sun, 25 Feb 2018 00:45:30 -0600 Subject: [PATCH 2/4] remove unused var --- service-tests/badge-json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service-tests/badge-json.js b/service-tests/badge-json.js index c42038bb5b837..03ca25b87b2ba 100644 --- a/service-tests/badge-json.js +++ b/service-tests/badge-json.js @@ -1,6 +1,6 @@ 'use strict'; -const Joi = require('joi'); +// const Joi = require('joi'); const ServiceTester = require('./runner/service-tester'); const { invalidJSON } = require('./helpers/response-fixtures'); From 33560502d68424c8dbd6d25e3a99259f522761c3 Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Sun, 25 Feb 2018 13:08:44 -0600 Subject: [PATCH 3/4] additional unit tests, testing colorB --- service-tests/badge-json.js | 71 ++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/service-tests/badge-json.js b/service-tests/badge-json.js index 03ca25b87b2ba..9bfa37c031898 100644 --- a/service-tests/badge-json.js +++ b/service-tests/badge-json.js @@ -6,6 +6,17 @@ const { invalidJSON } = require('./helpers/response-fixtures'); const t = new ServiceTester({ id: 'badge-json', title: 'badge json', pathPrefix: '/json' }); +function genJsonResponse(obj) { + return function() { + return [ + 200, + JSON.stringify(obj), + { 'Content-Type': 'application/json' } + ]; + }; +} + + module.exports = t; t.create('invalid urlencoding') @@ -29,26 +40,54 @@ t.create('invalid badge json') .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') .intercept(nock => nock('http://test.com') .get('/badge.json') - .reply(function() { - return [ - 200, - '{}', - { 'Content-Type': 'application/json' } - ]; - }) + .reply(genJsonResponse({})) ) .expectJSON({name: 'badge json', value: 'invalid badge-json'}); t.create('label & value') - .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json?style=_shields_test') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(genJsonResponse({label:"test", value:"success"})) + ) + .expectJSON({ + name: 'test', + value: 'success', + colorB: '#9f9f9f' // default + }); + +t.create('label, value, & class') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json?style=_shields_test') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(genJsonResponse({label:"test", value:"success", valueClass:"success"})) + ) + .expectJSON({ + name: 'test', + value: 'success', + colorB: '#4c1' // success + }); + + t.create('label, value, class, & colorB') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json?style=_shields_test') + .intercept(nock => nock('http://test.com') + .get('/badge.json') + .reply(genJsonResponse({label:"test", value:"success", valueClass:"success", colorB:"#D00D00"})) + ) + .expectJSON({ + name: 'test', + value: 'success', + colorB: '#D00D00' // colorB override + }); + + t.create('label, value, class, colorB, & colorB param') + .get('/http%3A%2F%2Ftest.com%2Fbadge.json.json?style=_shields_test&colorB=C001E0') .intercept(nock => nock('http://test.com') .get('/badge.json') - .reply(function() { - return [ - 200, - '{"label":"test","value":"success"}', - { 'Content-Type': 'application/json' } - ]; - }) + .reply(genJsonResponse({label:"test", value:"success", valueClass:"success", colorB:"#D00D00"})) ) - .expectJSON({name: 'test', value: 'success'}); + .expectJSON({ + name: 'test', + value: 'success', + colorB: '#C001E0' // colorB param + }); From b9f09486089e1e8dadd473579c5bf083279a0a1e Mon Sep 17 00:00:00 2001 From: Brad Kent Date: Mon, 26 Feb 2018 10:12:21 -0600 Subject: [PATCH 4/4] embettered badge-json documentation added notes to "dynamic" badge (what the params are) --- frontend/components/usage.js | 43 ++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/frontend/components/usage.js b/frontend/components/usage.js index 454152338ca95..dc256d142f14d 100644 --- a/frontend/components/usage.js +++ b/frontend/components/usage.js @@ -122,16 +122,28 @@ export default class Usage extends React.PureComponent {

      Badge JSON

      + +

      Your service/website can speak badge by serving badge-json

      +

      {baseUri}/json/<URL>.svg -
      - (<URL> must be url-encoded) +

      -

      Your service/website can speak badge by serving badge-json

      + + + + + + + +
      <URL> + URL that provides badge-json
      + must be url-encoded (above tool will url-encode for you) +

      badge-json is a simple object with the following properties/values.
      All Values are optional except for label and value @@ -191,10 +203,33 @@ export default class Usage extends React.PureComponent { /badge/dynamic/<TYPE>.svg?uri=<URI>&label=<LABEL>&query=<$.DATA.SUBDATA>&colorB=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

      + + + + + + + + + + + + + + + + +
      Notes
      query + Path to value in the json.
      + (see https://www.npmjs.com/package/jsonpath) +
      prefixprepended to value
      suffixappended to value
      + +
      +

      Parameters

      - Optional parameters you can use: (connecting several with "&" is possible) + All badges accept optional parameters: (connecting several with "&" is possible)