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

[badge-json] new "badge-json" service #1525

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions frontend/components/json-badge-maker.js
Original file line number Diff line number Diff line change
@@ -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 (
<form onSubmit={e => this.handleSubmit(e)}>
<input
className="medium"
value={this.state.url}
onChange={event => this.setState({ url: event.target.value })}
placeholder="url" /> {}
<button>Make Badge</button>
</form>
);
}
}
127 changes: 120 additions & 7 deletions frontend/components/usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -114,8 +115,86 @@ export default class Usage extends React.PureComponent {
</tbody>
</table>

<p>Where color is one of these named colors, or hex (xxxxxx)
</p>
{ this.renderColorExamples() }

<hr />

<h3 id="badge-json">Badge JSON</h3>

<p>Your service/website can speak badge by serving badge-json</p>

<JsonBadgeMaker baseUri={baseUri} />

<p>
<code>
{baseUri}/json/&lt;URL&gt;.svg
</code>
</p>

<table>
<tbody>
<tr>
<td style={{verticalAlign:'top'}}><code>&lt;URL&gt;</code></td>
<td>
URL that provides badge-json<br />
must be url-encoded (above tool will url-encode for you)
</td>
</tr>
</tbody>
</table>

<p>badge-json is a simple object with the following properties/values.<br />
All Values are optional except for <code>label</code> and <code>value</code>
</p>

<table>
<caption>Properties specified in JSON (* = required)</caption>
<tbody>
<tr>
<td><code>label</code> *</td>
<td>left-hand-side text</td>
</tr>
<tr>
<td><code>value</code> *</td>
<td>right-hand-side text</td>
</tr>
<tr>
<td><code>valueClass</code></td>
<td>one of &quot;error&quot;, &quot;notice&quot;, &quot;success&quot;, &quot;info&quot;, or &quot;default&quot;</td>
</tr>
<tr>
<td><code>isSocial</code></td>
<td>(boolean) default = <code>false</code>. If true, will get &quot;social&quot; style by default</td>
</tr>
<tr>
<td style={{verticalAlign:'top'}}><code>logo</code></td>
<td>
one of:
<ul>
<li><a href="https://github.com/badges/shields/tree/gh-pages/logo">named logo</a></li>
<li>data-uri: <code>data:image/png;base64,…</code></li>
</ul>
</td>
</tr>
<tr>
<td><code>logoWidth</code></td>
<td>Set the horizontal space to give to the logo</td>
</tr>
<tr>
<td><code>colorA</code></td>
<td>background color of the left part (overrides <code>valueClass</code> color scheme)</td>
</tr>
<tr>
<td><code>colorB</code></td>
<td>background color of the right part (overrides <code>valueClass</code> color scheme)</td>
</tr>
</tbody>
</table>

<hr />

<h3 id="dynamic-badge">Dynamic</h3>

<DynamicBadgeMaker baseUri={baseUri} />
Expand All @@ -124,17 +203,33 @@ export default class Usage extends React.PureComponent {
<code>/badge/dynamic/&lt;TYPE&gt;.svg?uri=&lt;URI&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;<a href="https://www.npmjs.com/package/jsonpath" target="_BLANK" title="JSONdata syntax">$.DATA.SUBDATA</a>&gt;&amp;colorB=&lt;COLOR&gt;&amp;prefix=&lt;PREFIX&gt;&amp;suffix=&lt;SUFFIX&gt;</code>
</p>

<hr className="spacing" />
<table>
<caption>Notes</caption>
<tbody>
<tr>
<td style={{verticalAlign:'top'}}><code>query</code></td>
<td>
Path to value in the json.<br />
(see <a href="https://www.npmjs.com/package/jsonpath">https://www.npmjs.com/package/jsonpath</a>)
</td>
</tr>
<tr>
<td><code>prefix</code></td>
<td>prepended to value</td>
</tr>
<tr>
<td><code>suffix</code></td>
<td>appended to value</td>
</tr>
</tbody>
</table>

<h2 id="styles">Styles</h2>
<hr />

<p>
The following styles are available (flat is the default as of Feb 1st 2015):
</p>
{ this.renderStyleExamples() }
<h2 id="parameters">Parameters</h2>

<p>
Here are a few other parameters you can use: (connecting several with "&" is possible)
All badges accept optional parameters: (connecting several with "&" is possible)
</p>
<table>
<tbody>
Expand Down Expand Up @@ -198,9 +293,27 @@ export default class Usage extends React.PureComponent {
</td>
<td>Set the HTTP cache lifetime in secs</td>
</tr>
<tr>
<td style={{verticalAlign:'top'}}>
<code>?style=flat</code>
</td>
<td>
Specify the badge style.<br />
One of : &quot;plastic&quot;, &quot;flat&quot;, &quot;flat-square&quot;, &quot;for-the-badge&quot;, or &quot;social&quot;
</td>
</tr>
</tbody>
</table>

<h3 id="styles">Styles</h3>

<p>
The following styles are available (flat is the default as of Feb 1st 2015):
</p>
{ this.renderStyleExamples() }

<hr className="spacing" />

<p>
We support <code>.svg</code>, <code>.json</code>, <code>.png</code> and a
few others, but use them responsibly.
Expand Down
5 changes: 5 additions & 0 deletions frontend/lib/badge-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {});
}
4 changes: 2 additions & 2 deletions lib/all-badge-examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const websiteDoc = `
</p>
<p>
<code>[OPTIONS]</code> can be:
<ul>
<ul class="options">
<li>
Nothing:&nbsp;
<code>…/website/…</code>
Expand Down Expand Up @@ -2141,7 +2141,7 @@ const allBadgeExamples = [
},
{
category: {
id: 'miscellaneous',
id: 'miscellaneous2',
name: 'Longer Miscellaneous'
},
examples: [
Expand Down
67 changes: 66 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)$/,
Copy link
Member

Choose a reason for hiding this comment

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

As I mentioned in #1525 (comment) let's make two or maybe three ways of leveraging the JSON functionality.

I'd prefer we add support for three endpoints:

  • /badge/dynamic/json-badge-data/https/example.com/info.json.svg (as an alternative to /json`)
  • /badge/dynamic/json-badge-data.svg?url=https%2Fexample.com%2Finfo.json
  • /badge/static.svg?badgeData=...

These support the more efficient static route for servers capable of building a redirect. That route can also be used in a dynamic web page, which is really cool! Simultaneously they make the "static" and "dynamic" distinctions very clear.

Copy link
Member

Choose a reason for hiding this comment

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

Good idea!

sidenote

I'm not much of a fan of the https/example.com endpoints,
just feels strange without the https://:
/badge/dynamic/json-badge-data/https://example.com/path/to.json.svg
/badge/dynamic/json-badge-data/https/example.com/path/to.json.svg
but we have a few that way already so for the sake of consistency it's probably best to continue that pattern.

Copy link
Member

Choose a reason for hiding this comment

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

Yea… I find it a little weird too. Though, I think a colon in a path is supposed to be URL encoded. Maybe we could just make the scheme optional and default to https? That way it would disappear in most cases…

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);
Copy link
Member

Choose a reason for hiding this comment

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

Could you move this transformation code into its own function in lib/, and make a list there of the supported parameters? It's great to have it in the web page, but feel like it ought to live with the code, too. By breaking this out into a separate (pure) function, it becomes very easy to test all this logic using data-driven tests. You'll see we've a bunch of these kind of tests now, using sazerac.

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({
Expand Down Expand Up @@ -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]);
Expand Down
Loading