Consolidate badge URL generation functions (#3032)

This consolidates all the badge URL generation functions into a single place, and updates the rest of the calling code to use it. It renames some things and updates labels to bring the names into alignment with the current API.

Resolve #2027
This commit is contained in:
Paul Melnikow
2019-02-21 21:07:27 -05:00
committed by GitHub
parent f550f0cc4d
commit 9e4a8cec09
14 changed files with 187 additions and 231 deletions

View File

@@ -71,9 +71,43 @@ function staticBadgeUrl({
return `${baseUrl}/badge/${path}.${format}${suffix}`
}
function dynamicBadgeUrl({
baseUrl,
datatype,
label,
dataUrl,
query,
prefix,
suffix,
color,
style,
format = 'svg',
}) {
const queryParams = {
label,
url: dataUrl,
query,
style,
}
if (color) {
queryParams.color = color
}
if (prefix) {
queryParams.prefix = prefix
}
if (suffix) {
queryParams.suffix = suffix
}
const outQueryString = queryString.stringify(queryParams)
return `${baseUrl}/badge/dynamic/${datatype}.${format}?${outQueryString}`
}
module.exports = {
badgeUrlFromPath,
badgeUrlFromPattern,
encodeField,
staticBadgeUrl,
dynamicBadgeUrl,
}

View File

@@ -6,6 +6,7 @@ const {
badgeUrlFromPattern,
encodeField,
staticBadgeUrl,
dynamicBadgeUrl,
} = require('./make-badge-url')
describe('Badge URL generation functions', function() {
@@ -79,4 +80,50 @@ describe('Badge URL generation functions', function() {
color: 'blue',
}).expect('/badge/-blue-blue.svg')
})
test(dynamicBadgeUrl, () => {
const dataUrl = 'http://example.com/foo.json'
const query = '$.bar'
const prefix = 'value: '
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
prefix,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'?label=foo',
`&prefix=${encodeURIComponent(prefix)}`,
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
const suffix = '<- value'
const color = 'blue'
given({
baseUrl: 'http://img.example.com',
datatype: 'json',
label: 'foo',
dataUrl,
query,
suffix,
color,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'?color=blue',
'&label=foo',
`&query=${encodeURIComponent(query)}`,
'&style=plastic',
`&suffix=${encodeURIComponent(suffix)}`,
`&url=${encodeURIComponent(dataUrl)}`,
].join('')
)
})
})

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import clipboardCopy from 'clipboard-copy'
import { staticBadgeUrl } from '../../lib/badge-url'
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { generateMarkup } from '../../lib/generate-image-markup'
import { Badge } from '../common'
import PathBuilder from './path-builder'
@@ -61,12 +61,11 @@ export default class Customizer extends React.Component {
if (pathIsComplete) {
src = this.generateBuiltBadgeUrl()
} else {
src = staticBadgeUrl(
src = staticBadgeUrl({
baseUrl,
'preview',
'some parameters missing',
'lightgray'
)
label: 'preview',
message: 'some parameters missing',
})
}
return (
<p>

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { staticBadgeUrl } from '../../lib/badge-url'
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { baseUrl } from '../../constants'
import Meta from '../meta'
import Header from '../header'
@@ -33,16 +33,24 @@ const NamedLogoTable = ({ logoNames }) => (
<td>
<Badge
alt={`logo: ${name}`}
src={staticBadgeUrl(baseUrl, 'named logo', name, 'blue', {
logo: name,
src={staticBadgeUrl({
baseUrl,
label: 'named logo',
message: name,
color: 'blue',
namedLogo: name,
})}
/>
</td>
<td>
<Badge
alt={`logo: ${name}`}
src={staticBadgeUrl(baseUrl, 'Named Logo', name, 'blue', {
logo: name,
src={staticBadgeUrl({
baseUrl,
label: 'Named Logo',
message: name,
color: 'blue',
namedLogo: name,
style: 'social',
})}
/>

View File

@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { dynamicBadgeUrl } from '../lib/badge-url'
import { dynamicBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { InlineInput } from './common'
export default class DynamicBadgeMaker extends React.Component {
@@ -11,7 +11,7 @@ export default class DynamicBadgeMaker extends React.Component {
state = {
datatype: '',
label: '',
url: '',
dataUrl: '',
query: '',
color: '',
prefix: '',
@@ -19,9 +19,22 @@ export default class DynamicBadgeMaker extends React.Component {
}
makeBadgeUrl() {
const { datatype, label, url, query, color, prefix, suffix } = this.state
const {
datatype,
label,
dataUrl,
query,
color,
prefix,
suffix,
} = this.state
const { baseUrl: baseUrl = document.location.href } = this.props
return dynamicBadgeUrl(baseUrl, datatype, label, url, query, {
return dynamicBadgeUrl({
baseUrl: baseUrl || window.location.href,
datatype,
label,
dataUrl,
query,
color,
prefix,
suffix,
@@ -34,8 +47,8 @@ export default class DynamicBadgeMaker extends React.Component {
}
get isValid() {
const { datatype, label, url, query } = this.state
return datatype && label && url && query
const { datatype, label, dataUrl, query } = this.state
return datatype && label && dataUrl && query
}
render() {
@@ -61,9 +74,9 @@ export default class DynamicBadgeMaker extends React.Component {
/>
<InlineInput
className="short"
onChange={event => this.setState({ url: event.target.value })}
placeholder="url"
value={this.state.url}
onChange={event => this.setState({ dataUrl: event.target.value })}
placeholder="data url"
value={this.state.dataUrl}
/>
<InlineInput
className="short"

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import resolveUrl from '../lib/resolve-url'
import { badgeUrlFromPath } from '../../core/badge-urls/make-badge-url'
import { H2 } from './common'
const SpacedA = styled.a`
@@ -16,42 +16,55 @@ const Footer = ({ baseUrl }) => (
<p>
<object
alt="Follow @shields_io"
data={resolveUrl(
'/twitter/follow/shields_io.svg?style=social&label=Follow',
baseUrl
)}
data={badgeUrlFromPath({
baseUrl,
path: '/twitter/follow/shields_io',
queryParams: { label: 'Follow' },
style: 'social',
})}
/>{' '}
{}
<object
alt="Donate to us!"
data={resolveUrl(
'/opencollective/backers/shields.svg?style=social&link=https://opencollective.com/shields',
baseUrl
)}
data={badgeUrlFromPath({
baseUrl,
path: '/opencollective/backers/shields',
queryParams: { link: 'https://opencollective.com/shields' },
style: 'social',
})}
/>{' '}
{}
<object
alt="Donate to us!"
data={resolveUrl(
'/opencollective/sponsors/shields.svg?style=social&link=https://opencollective.com/shields',
baseUrl
)}
data={badgeUrlFromPath({
baseUrl,
path: '/opencollective/sponsors/shields',
queryParams: { link: 'https://opencollective.com/shields' },
style: 'social',
})}
/>{' '}
{}
<object
alt="Fork on GitHub"
data={resolveUrl(
'/github/forks/badges/shields.svg?style=social&label=Fork',
baseUrl
)}
data={badgeUrlFromPath({
baseUrl,
path: '/github/forks/badges/shields',
queryParams: { label: 'Fork' },
style: 'social',
})}
/>{' '}
{}
<object
alt="chat on Discord"
data={resolveUrl(
'/discord/308323056592486420.svg?style=social&label=Chat&link=https://discord.gg/HjJCwm5',
baseUrl
)}
data={badgeUrlFromPath({
baseUrl,
path: '/discord/308323056592486420',
queryParams: {
label: 'Chat',
link: 'link=https://discord.gg/HjJCwm5',
},
style: 'social',
})}
/>
</p>

View File

@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { staticBadgeUrl } from '../lib/badge-url'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { InlineInput } from './common'
export default class StaticBadgeMaker extends React.Component {
@@ -9,8 +9,8 @@ export default class StaticBadgeMaker extends React.Component {
}
state = {
subject: '',
status: '',
label: '',
message: '',
color: '',
}
@@ -18,13 +18,13 @@ export default class StaticBadgeMaker extends React.Component {
e.preventDefault()
const { baseUrl } = this.props
const { subject, status, color } = this.state
const badgeUrl = staticBadgeUrl(
baseUrl || window.location.href,
subject,
status,
color
)
const { label, message, color } = this.state
const badgeUrl = staticBadgeUrl({
baseUrl: baseUrl || window.location.href,
label,
message,
color,
})
document.location = badgeUrl
}
@@ -33,14 +33,14 @@ export default class StaticBadgeMaker extends React.Component {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<InlineInput
onChange={event => this.setState({ subject: event.target.value })}
placeholder="subject"
value={this.state.subject}
onChange={event => this.setState({ label: event.target.value })}
placeholder="label"
value={this.state.label}
/>
<InlineInput
onChange={event => this.setState({ status: event.target.value })}
placeholder="status"
value={this.state.status}
onChange={event => this.setState({ message: event.target.value })}
placeholder="message"
value={this.state.message}
/>
<InlineInput
list="default-colors"

View File

@@ -2,7 +2,6 @@ import React from 'react'
import PropTypes from 'prop-types'
import fetchPonyfill from 'fetch-ponyfill'
import debounce from 'lodash.debounce'
import resolveUrl from '../lib/resolve-url'
import BadgeExamples from './badge-examples'
import { BlockInput } from './common'
@@ -42,7 +41,7 @@ export default class SuggestionAndSearch extends React.Component {
const { baseUrl } = this.props
const { projectUrl } = this.state
const url = resolveUrl('/$suggest/v1', baseUrl, { url: projectUrl })
const url = `${baseUrl}/$suggest/v1?url=${encodeURIComponent(projectUrl)}`
const fetch = window.fetch || fetchPonyfill
const res = await fetch(url)

View File

@@ -2,7 +2,7 @@ import React from 'react'
import { Link } from 'gatsby'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { staticBadgeUrl } from '../lib/badge-url'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { advertisedStyles, shieldsLogos } from '../../supported-features.json'
import StaticBadgeMaker from './static-badge-maker'
import DynamicBadgeMaker from './dynamic-badge-maker'
@@ -69,7 +69,7 @@ const ColorExamples = ({ baseUrl, colors }) => (
<Badge
alt={color}
key={color}
src={staticBadgeUrl(baseUrl, '', color, color)}
src={staticBadgeUrl({ baseUrl, label: '', message: color, color })}
/>
))}
</span>
@@ -91,8 +91,12 @@ export default class Usage extends React.PureComponent {
<tbody>
{advertisedStyles.map(style => {
const snippet = `?style=${style}&logo=appveyor`
const badgeUrl = staticBadgeUrl(baseUrl, 'style', style, 'green', {
logo: 'appveyor',
const badgeUrl = staticBadgeUrl({
baseUrl,
label: 'style',
message: style,
color: 'green',
namedLogo: 'appveyor',
style,
})
return (

View File

@@ -1,53 +0,0 @@
import resolveUrl from './resolve-url'
import { staticBadgeUrl as makeStaticBadgeUrl } from '../../core/badge-urls/make-badge-url'
export default function resolveBadgeUrl(
url,
baseUrl,
{ longCache, style, queryParams: inQueryParams, format = 'svg' } = {}
) {
const outQueryParams = Object.assign({}, inQueryParams)
if (longCache) {
outQueryParams.maxAge = '2592000'
}
if (style) {
outQueryParams.style = style
}
return resolveUrl(`${url}.${format}`, baseUrl, outQueryParams)
}
export function staticBadgeUrl(baseUrl, label, message, color, options) {
const path = makeStaticBadgeUrl({ label, message, color })
return resolveUrl(path, baseUrl, options)
}
// Options can include: { prefix, suffix, color, longCache, style, queryParams }
export function dynamicBadgeUrl(
baseUrl,
datatype,
label,
dataUrl,
query,
{ prefix, suffix, color, queryParams = {}, ...rest } = {}
) {
Object.assign(queryParams, {
label,
url: dataUrl,
query,
})
if (color) {
queryParams.color = color
}
if (prefix) {
queryParams.prefix = prefix
}
if (suffix) {
queryParams.suffix = suffix
}
const outOptions = Object.assign({ queryParams }, rest)
return resolveBadgeUrl(`/badge/dynamic/${datatype}`, baseUrl, outOptions)
}

View File

@@ -1,67 +0,0 @@
import { test, given } from 'sazerac'
import resolveBadgeUrl, { staticBadgeUrl, dynamicBadgeUrl } from './badge-url'
const resolveBadgeUrlWithLongCache = (url, baseUrl) =>
resolveBadgeUrl(url, baseUrl, { longCache: true })
describe('Badge URL functions', function() {
test(resolveBadgeUrl, () => {
given('/badge/foo-bar-blue', undefined).expect('/badge/foo-bar-blue.svg')
given('/badge/foo-bar-blue', 'http://example.com').expect(
'http://example.com/badge/foo-bar-blue.svg'
)
})
test(resolveBadgeUrlWithLongCache, () => {
given('/badge/foo-bar-blue', undefined).expect(
'/badge/foo-bar-blue.svg?maxAge=2592000'
)
given('/badge/foo-bar-blue', 'http://example.com').expect(
'http://example.com/badge/foo-bar-blue.svg?maxAge=2592000'
)
})
test(staticBadgeUrl, () => {
given('http://img.example.com', 'foo', 'bar', 'blue', {
style: 'plastic',
}).expect('http://img.example.com/badge/foo-bar-blue.svg?style=plastic')
})
test(dynamicBadgeUrl, () => {
const dataUrl = 'http://example.com/foo.json'
const query = '$.bar'
const prefix = 'value: '
given('http://img.example.com', 'json', 'foo', dataUrl, query, {
prefix,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'?label=foo',
`&url=${encodeURIComponent(dataUrl)}`,
`&query=${encodeURIComponent(query)}`,
`&prefix=${encodeURIComponent(prefix)}`,
'&style=plastic',
].join('')
)
const suffix = '<- value'
const color = 'blue'
given('http://img.example.com', 'json', 'foo', dataUrl, query, {
suffix,
color,
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'?label=foo',
`&url=${encodeURIComponent(dataUrl)}`,
`&query=${encodeURIComponent(query)}`,
'&color=blue',
`&suffix=${encodeURIComponent(suffix)}`,
'&style=plastic',
].join('')
)
})
})

View File

@@ -1,17 +0,0 @@
// I played with build-url and url-resolve-browser and neither of them did the
// right thing. Previously this was based on url-path, which patched around
// the URL API. This caused problems in Firefox 57, but only in the production
// build.
// Let's rewrite these without a deprecated API!
// eslint-disable-next-line node/no-deprecated-api
import { resolve, parse, format } from 'url'
// baseUrl and queryParams are optional.
export default function resolveUrl(url, baseUrl, queryParams) {
const resolved = baseUrl ? resolve(baseUrl, url) : url
const parsed = parse(resolved, /* parseQueryString */ true)
parsed.query = Object.assign({}, parsed.query, queryParams)
delete parsed.search
return format(parsed)
}

View File

@@ -1,29 +0,0 @@
import { test, given, forCases } from 'sazerac'
import resolveUrl from './resolve-url'
describe('URL resolver', function() {
test(resolveUrl, () => {
forCases([
given('/foo/bar'),
given('/foo/bar', '/'),
given('/foo/bar', '/baz'),
given('/foo/bar', '/baz/'),
given('/foo/bar', ''),
given('/foo/bar', undefined),
]).expect('/foo/bar')
given('foo/bar', '/baz/').expect('/baz/foo/bar')
forCases([
given('http://foo/bar'),
given('bar', 'http://foo/'),
given('/bar', 'http://foo/'),
]).expect('http://foo/bar')
given('/foo/bar', '/baz', { baz: 'bazinga' }).expect('/foo/bar?baz=bazinga')
given('/foo/bar?thing=1', undefined, { other: '2' }).expect(
'/foo/bar?thing=1&other=2'
)
})
})

View File

@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { staticBadgeUrl } from '../lib/badge-url'
import { staticBadgeUrl } from '../../core/badge-urls/make-badge-url'
import { baseUrl } from '../constants'
import Meta from '../components/meta'
import Header from '../components/header'
@@ -101,7 +101,12 @@ const EndpointPage = () => (
<p>Shields response:</p>
<Badge
alt="hello | sweet world"
src={staticBadgeUrl(baseUrl, 'hello', 'sweet world', 'orange')}
src={staticBadgeUrl({
baseUrl,
label: 'hello',
message: 'sweet world',
color: 'orange',
})}
/>
<Explanation>
<p>