-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reimplements the idea @bkdotcom came up with in #1519, and took a stab at in #1525. It’s a really powerful way to add all sorts of custom badges, particularly considering [tools like RunKit endpoints and Jupyter Kernel Gateway](#2259 (comment)), not to mention all the other ways cloud functions can be deployed these days. Ref #1752 #2259
- Loading branch information
1 parent
d0c9da0
commit 1aacf7e
Showing
5 changed files
with
327 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import React from 'react' | ||
import { Link } from 'react-router-dom' | ||
import Meta from './meta' | ||
import Header from './header' | ||
import Footer from './footer' | ||
import { baseUrl } from '../constants' | ||
|
||
const example = JSON.stringify( | ||
{ | ||
schemaVersion: 1, | ||
label: 'hello', | ||
message: 'sweet world', | ||
color: 'orange', | ||
}, | ||
undefined, | ||
2 | ||
) | ||
|
||
const EndpointPage = ({ baseUrl }) => ( | ||
<div> | ||
<Meta /> | ||
<Header /> | ||
|
||
<h3 id="static-badge">Endpoint</h3> | ||
|
||
<p> | ||
<code> | ||
{baseUrl} | ||
/badge/endpoint.svg?url=<URL>&style=<STYLE> | ||
</code> | ||
</p> | ||
|
||
<p style={{ textAlign: 'left' }}> | ||
Using the endpoint badge, you can create badges from your own JSON | ||
endpoint. | ||
</p> | ||
|
||
<p>The endpoint must return an object like this:</p> | ||
|
||
<code | ||
style={{ | ||
display: 'block', | ||
width: '250px', | ||
margin: '0 auto', | ||
padding: '10px 30px', | ||
textAlign: 'left', | ||
}} | ||
> | ||
{example} | ||
</code> | ||
|
||
<h4>Schema</h4> | ||
|
||
<style jsx>{` | ||
.schema { | ||
display: inline-block; | ||
overflow: hidden; | ||
text-align: left; | ||
background: #efefef; | ||
padding: 10px; | ||
max-width: 800px; | ||
} | ||
.schema dt, | ||
.schema dd { | ||
padding: 0 1%; | ||
margin-top: 8px; | ||
margin-bottom: 8px; | ||
float: left; | ||
} | ||
.schema dt { | ||
width: 100px; | ||
clear: both; | ||
} | ||
.schema dd { | ||
margin-left: 20px; | ||
width: 75%; | ||
} | ||
@media (max-width: 600px) { | ||
.data_table { | ||
text-align: center; | ||
} | ||
} | ||
`}</style> | ||
|
||
<dl className="schema"> | ||
<dt>schemaVersion</dt> | ||
<dd> | ||
Required. Always the number <code>1</code>. | ||
</dd> | ||
<dt>label</dt> | ||
<dd> | ||
Required. The left text, or the empty string to omit the left side of | ||
the badge. This can be overridden by the query string. | ||
</dd> | ||
<dt>message</dt> | ||
<dd>Required. Can't be empty. The right text.</dd> | ||
<dt>color</dt> | ||
<dd> | ||
Default: <code>lightgrey</code>. The right color. Supports the eight | ||
named colors above, as well as hex, rgb, rgba, hsl, hsla and css named | ||
colors. | ||
</dd> | ||
<dt>labelColor</dt> | ||
<dd> | ||
Default: <code>grey</code>. The left color. | ||
</dd> | ||
<dt>isError</dt> | ||
<dd> | ||
Default: <code>false</code>. <code>true</code> to treat this as an error | ||
badge. In the future this will inhibit the query string from overriding | ||
the color and may affect cache behavior. | ||
</dd> | ||
<dt>link</dt> | ||
<dd> | ||
Default: none. Specify what clicking on the left/right of a badge should | ||
do. | ||
</dd> | ||
<dt>namedLogo</dt> | ||
<dd> | ||
Default: none. One of the named logos supported by Shields or {} | ||
<a href="https://simpleicons.org/">simple-icons</a>. Can be overridden | ||
by the query string. | ||
</dd> | ||
<dt>logoSvg</dt> | ||
<dd>Default: none. An SVG string containing a custom logo.</dd> | ||
<dt>logoColor</dt> | ||
<dd> | ||
Default: none. Same meaning as the query string. Can be overridden by | ||
the query string. | ||
</dd> | ||
<dt>logoWidth</dt> | ||
<dd> | ||
Default: none. Same meaning as the query string. Can be overridden by | ||
the query string. | ||
</dd> | ||
<dt>logoPosition</dt> | ||
<dd> | ||
Default: none. Same meaning as the query string. Can be overridden by | ||
the query string. | ||
</dd> | ||
<dt>style</dt> | ||
<dd> | ||
Default: <code>flat</code>. The default template to use. Can be | ||
overridden by the query string. | ||
</dd> | ||
<dt>cacheSeconds</dt> | ||
<dd> | ||
Default: <code>300</code>. Set the HTTP cache lifetime in seconds, which | ||
should respected by the Shields' CDN and downstream users. This lets you | ||
tune performance and traffic vs. responsiveness. Can be overridden by | ||
the query string, but only to a larger value. | ||
</dd> | ||
</dl> | ||
<br style={{ clear: 'both' }} /> | ||
<Footer /> | ||
</div> | ||
) | ||
export default EndpointPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
'use strict' | ||
|
||
const { URL } = require('url') | ||
const Joi = require('joi') | ||
const { errorMessages } = require('../dynamic/dynamic-helpers') | ||
const BaseJsonService = require('../base-json') | ||
const { InvalidParameter } = require('../errors') | ||
const { optionalUrl } = require('../validators') | ||
|
||
const blockedDomains = ['github.com', 'shields.io'] | ||
|
||
const queryParamSchema = Joi.object({ | ||
url: optionalUrl.required(), | ||
}).required() | ||
|
||
const endpointSchema = Joi.object({ | ||
schemaVersion: 1, | ||
label: Joi.string() | ||
.allow('') | ||
.required(), | ||
message: Joi.string().required(), | ||
color: Joi.string(), | ||
labelColor: Joi.string(), | ||
isError: Joi.boolean().default(false), | ||
link: Joi.string(), | ||
namedLogo: Joi.string(), | ||
logoSvg: Joi.string(), | ||
logoColor: Joi.forbidden(), | ||
logoWidth: Joi.forbidden(), | ||
logoPosition: Joi.forbidden(), | ||
style: Joi.string(), | ||
cacheSeconds: Joi.number(), | ||
}) | ||
.oxor('namedLogo', 'logoSvg') | ||
.when( | ||
Joi.alternatives().try( | ||
Joi.object({ namedLogo: Joi.string().required() }).unknown(), | ||
Joi.object({ logoSvg: Joi.string().required() }).unknown() | ||
), | ||
{ | ||
then: Joi.object({ | ||
logoColor: Joi.string(), | ||
logoWidth: Joi.number(), | ||
logoPosition: Joi.number(), | ||
}), | ||
} | ||
) | ||
.required() | ||
|
||
module.exports = class Endpoint extends BaseJsonService { | ||
static get category() { | ||
return 'dynamic' | ||
} | ||
|
||
static get route() { | ||
return { | ||
base: 'badge/endpoint', | ||
pattern: '', | ||
queryParams: ['url'], | ||
} | ||
} | ||
|
||
static get _cacheLength() { | ||
return 300 | ||
} | ||
|
||
static get defaultBadgeData() { | ||
return { | ||
label: 'custom badge', | ||
} | ||
} | ||
|
||
static render({ | ||
label, | ||
message, | ||
color, | ||
labelColor, | ||
namedLogo, | ||
logoSvg, | ||
logoColor, | ||
logoWidth, | ||
logoPosition, | ||
style, | ||
isError, | ||
cacheSeconds, | ||
}) { | ||
return { | ||
label, | ||
message, | ||
color, | ||
} | ||
} | ||
|
||
async handle(namedParams, queryParams) { | ||
const { url } = this.constructor._validateQueryParams( | ||
queryParams, | ||
queryParamSchema | ||
) | ||
|
||
const { protocol, hostname } = new URL(url) | ||
if (protocol !== 'https:') { | ||
throw new InvalidParameter({ prettyMessage: 'please use https' }) | ||
} | ||
if (blockedDomains.some(domain => hostname.endsWith(domain))) { | ||
throw new InvalidParameter({ prettyMessage: 'domain is blocked' }) | ||
} | ||
|
||
const data = await this._requestJson({ | ||
schema: endpointSchema, | ||
url, | ||
errorMessages, | ||
}) | ||
|
||
return this.constructor.render(data) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
'use strict' | ||
|
||
const t = (module.exports = require('../create-service-tester')()) | ||
|
||
t.create('Valid schema (mocked)') | ||
.get('.json?url=https://example.com/badge') | ||
.only() | ||
.intercept(nock => | ||
nock('https://example.com/') | ||
.get('/badge') | ||
.reply(200, { | ||
schemaVersion: 1, | ||
label: '', | ||
message: 'yo', | ||
}) | ||
) | ||
.expectJSON({ name: '', value: 'yo' }) | ||
|
||
t.create('Invalid schema (mocked)') | ||
.get('.json?url=https://example.com/badge') | ||
.intercept(nock => | ||
nock('https://example.com/') | ||
.get('/badge') | ||
.reply(200, { | ||
schemaVersion: -1, | ||
}) | ||
) | ||
.expectJSON({ name: 'custom badge', value: 'invalid response data' }) | ||
|
||
t.create('Bad scheme') | ||
.get('.json?url=http://example.com/badge') | ||
.expectJSON({ name: 'custom badge', value: 'please use https' }) | ||
|
||
t.create('Blocked domain') | ||
.get('.json?url=https://img.shields.io/badge/foo-bar-blue.json') | ||
.expectJSON({ name: 'custom badge', value: 'domain is blocked' }) |