Badge suggestion feature fix (#3331)
* Display suggested badges * E2e test for badge suggestion * Suggest resource returns example with pattern * Do not require preview in MarkupModalContent * Skip integration test for suggestion * Unmodifiable path in customizer * Use suggested link * Allow to change suggested badges * Enable skipped test * Enable skipped test * Code refactoring * Code refactoring * Code refactoring * Code refactoring * Code refactoring * Code refactoring * Unused code removed * Unused code removed * getExampleWithServiceByPattern helper added * BadgeExamples uses examples instead of services definitions * Revert "getExampleWithServiceByPattern helper added" This reverts commit80839fd705. * style removed from example * example.exact replaced with preview.buildFromExample * keywords are required again * Code refactoring * More e2e tests for suggestion feature * Code refactoring * Build add with a base url * showActualParams -> isPrefilled * A new schema for BadgeExamples * Link moved to queryParams * Updated documentation for the suggest reponse format * Link moved to queryParams - another test updated * Revert "Link moved to queryParams - another test updated" This reverts commitb5f811bb07. * Revert "Link moved to queryParams" This reverts commit3b54c6d2b4. * Disable changes in path in suggested badges * 'link' element documentation restored
This commit is contained in:
@@ -314,7 +314,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Frontend build
|
||||
command: npm run build
|
||||
command: GATSBY_BASE_URL=http://localhost:8080 npm run build
|
||||
|
||||
- run:
|
||||
name: Run tests
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"fixturesFolder": false,
|
||||
"pluginsFile": false,
|
||||
"supportFile": false
|
||||
"supportFile": false,
|
||||
"env": {
|
||||
"backend_url": "http://localhost:8080"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
describe('Main page', function() {
|
||||
const backendUrl = Cypress.env('backend_url')
|
||||
const SEARCH_INPUT = 'input[placeholder="search / project URL"]'
|
||||
|
||||
it('Search for badges', function() {
|
||||
cy.visit('/')
|
||||
|
||||
cy.get('input[placeholder="search / project URL"]').type('pypi')
|
||||
cy.get(SEARCH_INPUT).type('pypi')
|
||||
|
||||
cy.contains('PyPI - License')
|
||||
})
|
||||
|
||||
it('Suggest badges', function() {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
|
||||
cy.visit('/')
|
||||
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
|
||||
cy.contains('GitHub issues')
|
||||
cy.get(`img[src='${badgeUrl}']`)
|
||||
cy.contains(badgeUrl)
|
||||
})
|
||||
|
||||
it('Customization form is filled with suggested badge details', function() {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
|
||||
cy.visit('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
|
||||
cy.contains(badgeUrl).click()
|
||||
|
||||
cy.get('input[name="user"]').should('have.value', 'badges')
|
||||
cy.get('input[name="repo"]').should('have.value', 'shields')
|
||||
})
|
||||
|
||||
it('Customizate suggested badge', function() {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
|
||||
cy.visit('/')
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
cy.contains(badgeUrl).click()
|
||||
|
||||
cy.get('table input[name="color"]').type('orange')
|
||||
|
||||
cy.get(
|
||||
`img[src='${backendUrl}/github/issues/badges/shields.svg?color=orange']`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,13 +2,10 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
staticBadgeUrl,
|
||||
} from '../../core/badge-urls/make-badge-url'
|
||||
import {
|
||||
serviceDefinitionPropType,
|
||||
examplePropType,
|
||||
} from '../lib/service-definitions/service-definition-prop-types'
|
||||
import { examplePropType } from '../lib/service-definitions/example-prop-types'
|
||||
import { Badge } from './common'
|
||||
import { StyledCode } from './snippet'
|
||||
|
||||
@@ -33,24 +30,29 @@ const ClickableCode = styled(StyledCode)`
|
||||
function Example({ baseUrl, onClick, exampleData }) {
|
||||
const { title, example, preview } = exampleData
|
||||
|
||||
const { label, message, color, style, namedLogo } = preview
|
||||
const previewUrl = staticBadgeUrl({
|
||||
baseUrl,
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
style,
|
||||
namedLogo,
|
||||
})
|
||||
|
||||
const { pattern, namedParams, queryParams } = example
|
||||
const exampleUrl = badgeUrlFromPath({
|
||||
const exampleUrl = badgeUrlFromPattern({
|
||||
baseUrl,
|
||||
path: pattern,
|
||||
pattern,
|
||||
namedParams,
|
||||
queryParams,
|
||||
})
|
||||
|
||||
let previewUrl
|
||||
if (preview.buildFromExample) {
|
||||
previewUrl = exampleUrl
|
||||
} else {
|
||||
const { label, message, color, style, namedLogo } = preview
|
||||
previewUrl = staticBadgeUrl({
|
||||
baseUrl,
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
style,
|
||||
namedLogo,
|
||||
})
|
||||
}
|
||||
|
||||
const handleClick = () => onClick(exampleData)
|
||||
|
||||
return (
|
||||
@@ -71,16 +73,11 @@ Example.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default function BadgeExamples({ definitions, baseUrl, onClick }) {
|
||||
const flattened = definitions.reduce((accum, current) => {
|
||||
const { examples } = current
|
||||
return accum.concat(examples)
|
||||
}, [])
|
||||
|
||||
export default function BadgeExamples({ examples, baseUrl, onClick }) {
|
||||
return (
|
||||
<ExampleTable>
|
||||
<tbody>
|
||||
{flattened.map(exampleData => (
|
||||
{examples.map(exampleData => (
|
||||
<Example
|
||||
baseUrl={baseUrl}
|
||||
exampleData={exampleData}
|
||||
@@ -93,7 +90,7 @@ export default function BadgeExamples({ definitions, baseUrl, onClick }) {
|
||||
)
|
||||
}
|
||||
BadgeExamples.propTypes = {
|
||||
definitions: PropTypes.arrayOf(serviceDefinitionPropType).isRequired,
|
||||
examples: PropTypes.arrayOf(examplePropType).isRequired,
|
||||
baseUrl: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
@@ -6,100 +6,84 @@ import '../enzyme-conf.spec'
|
||||
|
||||
const exampleServiceDefinitions = [
|
||||
{
|
||||
examples: [
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/d/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'downloads',
|
||||
message: '12k',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/d/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
],
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'downloads',
|
||||
message: '12k',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
{
|
||||
examples: [
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/rating/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'rating',
|
||||
message: '4/5',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/rating/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/stars/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'stars',
|
||||
message: '★★★★☆',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
],
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'rating',
|
||||
message: '4/5',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
{
|
||||
examples: [
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/users/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'users',
|
||||
message: '750',
|
||||
color: 'blue',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/stars/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
],
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'stars',
|
||||
message: '★★★★☆',
|
||||
color: 'brightgreen',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
{
|
||||
examples: [
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/v/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'mozilla add-on',
|
||||
message: 'v2.1.0',
|
||||
color: 'blue',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/users/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
],
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'users',
|
||||
message: '750',
|
||||
color: 'blue',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
{
|
||||
title: 'Mozilla Add-on',
|
||||
example: {
|
||||
pattern: '/amo/v/:addonId',
|
||||
namedParams: {
|
||||
addonId: 'dustman',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
label: 'mozilla add-on',
|
||||
message: 'v2.1.0',
|
||||
color: 'blue',
|
||||
},
|
||||
keywords: ['amo', 'firefox'],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -108,7 +92,7 @@ describe('<BadgeExamples />', function() {
|
||||
shallow(
|
||||
<BadgeExamples
|
||||
baseUrl="https://example.shields.io"
|
||||
definitions={[]}
|
||||
examples={[]}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
)
|
||||
@@ -118,7 +102,7 @@ describe('<BadgeExamples />', function() {
|
||||
shallow(
|
||||
<BadgeExamples
|
||||
baseUrl="https://example.shields.io"
|
||||
definitions={exampleServiceDefinitions}
|
||||
examples={exampleServiceDefinitions}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -18,16 +18,12 @@ export default class Customizer extends React.Component {
|
||||
exampleNamedParams: objectOfKeyValuesPropType,
|
||||
exampleQueryParams: objectOfKeyValuesPropType,
|
||||
initialStyle: PropTypes.string,
|
||||
isPrefilled: PropTypes.bool,
|
||||
link: PropTypes.string,
|
||||
}
|
||||
|
||||
indicatorRef = React.createRef()
|
||||
|
||||
state = {
|
||||
path: '',
|
||||
link: '',
|
||||
message: undefined,
|
||||
}
|
||||
|
||||
get baseUrl() {
|
||||
const { baseUrl } = this.props
|
||||
if (baseUrl) {
|
||||
@@ -129,18 +125,29 @@ export default class Customizer extends React.Component {
|
||||
this.setState({ queryString })
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
link: this.props.link || '',
|
||||
message: undefined,
|
||||
path: '',
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
pattern,
|
||||
exampleNamedParams,
|
||||
exampleQueryParams,
|
||||
initialStyle,
|
||||
isPrefilled,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<form action="">
|
||||
<PathBuilder
|
||||
exampleParams={exampleNamedParams}
|
||||
isPrefilled={isPrefilled}
|
||||
onChange={this.handlePathChange}
|
||||
pattern={pattern}
|
||||
/>
|
||||
|
||||
@@ -73,6 +73,7 @@ export default class PathBuilder extends React.Component {
|
||||
pattern: PropTypes.string.isRequired,
|
||||
exampleParams: objectOfKeyValuesPropType,
|
||||
onChange: PropTypes.func,
|
||||
isPrefilled: PropTypes.bool,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
@@ -81,17 +82,20 @@ export default class PathBuilder extends React.Component {
|
||||
const { pattern } = props
|
||||
const tokens = pathToRegexp.parse(pattern)
|
||||
|
||||
const namedParams = {}
|
||||
|
||||
// `pathToRegexp.parse()` returns a mixed array of strings for literals
|
||||
// and objects for parameters. Filter out the literals and work with the
|
||||
// objects.
|
||||
tokens
|
||||
.filter(t => typeof t !== 'string')
|
||||
.forEach(({ name }) => {
|
||||
namedParams[name] = ''
|
||||
})
|
||||
|
||||
let namedParams
|
||||
if (this.props.isPrefilled) {
|
||||
namedParams = this.props.exampleParams
|
||||
} else {
|
||||
namedParams = {}
|
||||
// `pathToRegexp.parse()` returns a mixed array of strings for literals
|
||||
// and objects for parameters. Filter out the literals and work with the
|
||||
// objects.
|
||||
tokens
|
||||
.filter(t => typeof t !== 'string')
|
||||
.forEach(({ name }) => {
|
||||
namedParams[name] = ''
|
||||
})
|
||||
}
|
||||
this.state = {
|
||||
tokens,
|
||||
namedParams,
|
||||
@@ -173,11 +177,15 @@ export default class PathBuilder extends React.Component {
|
||||
onChange={this.handleTokenChange}
|
||||
value={value}
|
||||
>
|
||||
<option key="empty" value="">
|
||||
<option disabled={this.props.isPrefilled} key="empty" value="">
|
||||
{' '}
|
||||
</option>
|
||||
{options.map(option => (
|
||||
<option key={option} value={option}>
|
||||
<option
|
||||
disabled={this.props.isPrefilled}
|
||||
key={option}
|
||||
value={option}
|
||||
>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
@@ -186,6 +194,7 @@ export default class PathBuilder extends React.Component {
|
||||
} else {
|
||||
return (
|
||||
<NamedParamInput
|
||||
disabled={this.props.isPrefilled}
|
||||
name={name}
|
||||
onChange={this.handleTokenChange}
|
||||
type="text"
|
||||
@@ -211,9 +220,11 @@ export default class PathBuilder extends React.Component {
|
||||
{optional ? <BuilderLabel>(optional)</BuilderLabel> : null}
|
||||
</NamedParamLabelContainer>
|
||||
{this.renderNamedParamInput(token)}
|
||||
<NamedParamCaption>
|
||||
{namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue}
|
||||
</NamedParamCaption>
|
||||
{!this.props.isPrefilled && (
|
||||
<NamedParamCaption>
|
||||
{namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue}
|
||||
</NamedParamCaption>
|
||||
)}
|
||||
</PathBuilderColumn>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -104,12 +104,25 @@ export default class Main extends React.Component {
|
||||
renderCategory(category, definitions) {
|
||||
const { id } = category
|
||||
|
||||
const flattened = definitions
|
||||
.reduce((accum, current) => {
|
||||
const { examples } = current
|
||||
return accum.concat(examples)
|
||||
}, [])
|
||||
.map(({ title, link, example, preview, documentation }) => ({
|
||||
title,
|
||||
link,
|
||||
example,
|
||||
preview,
|
||||
documentation,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div key={id}>
|
||||
<CategoryHeading category={category} />
|
||||
<BadgeExamples
|
||||
baseUrl={baseUrl}
|
||||
definitions={definitions}
|
||||
examples={flattened}
|
||||
onClick={this.handleExampleSelected}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Modal from 'react-modal'
|
||||
import styled from 'styled-components'
|
||||
import { examplePropType } from '../../lib/service-definitions/service-definition-prop-types'
|
||||
import { examplePropType } from '../../lib/service-definitions/example-prop-types'
|
||||
import { BaseFont } from '../common'
|
||||
import MarkupModalContent from './markup-modal-content'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import { examplePropType } from '../../lib/service-definitions/service-definition-prop-types'
|
||||
import { examplePropType } from '../../lib/service-definitions/example-prop-types'
|
||||
import { H3 } from '../common'
|
||||
import Customizer from '../customizer/customizer'
|
||||
|
||||
@@ -31,7 +31,8 @@ export default class MarkupModalContent extends React.Component {
|
||||
example: {
|
||||
title,
|
||||
example: { pattern, namedParams, queryParams },
|
||||
preview: { style: initialStyle },
|
||||
link,
|
||||
preview: { style: initialStyle, buildFromExample } = {},
|
||||
},
|
||||
baseUrl,
|
||||
} = this.props
|
||||
@@ -44,6 +45,8 @@ export default class MarkupModalContent extends React.Component {
|
||||
exampleNamedParams={namedParams}
|
||||
exampleQueryParams={queryParams}
|
||||
initialStyle={initialStyle}
|
||||
isPrefilled={buildFromExample}
|
||||
link={link}
|
||||
pattern={pattern}
|
||||
title={title}
|
||||
/>
|
||||
|
||||
@@ -61,27 +61,26 @@ export default class SuggestionAndSearch extends React.Component {
|
||||
|
||||
renderSuggestions() {
|
||||
const { baseUrl } = this.props
|
||||
const { suggestions } = this.state
|
||||
let { suggestions } = this.state
|
||||
|
||||
if (suggestions.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const transformed = [
|
||||
{
|
||||
examples: suggestions.map(({ title, path, link, queryParams }) => ({
|
||||
title,
|
||||
preview: { path, queryParams },
|
||||
example: { path, queryParams },
|
||||
link,
|
||||
})),
|
||||
},
|
||||
]
|
||||
suggestions = suggestions.map(
|
||||
({ title, link, example, preview, documentation }) => ({
|
||||
title,
|
||||
link,
|
||||
example,
|
||||
preview: { ...preview, buildFromExample: true },
|
||||
documentation,
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<BadgeExamples
|
||||
baseUrl={baseUrl}
|
||||
definitions={transformed}
|
||||
examples={suggestions}
|
||||
onClick={this.props.onBadgeClick}
|
||||
/>
|
||||
)
|
||||
|
||||
27
frontend/lib/service-definitions/example-prop-types.js
Normal file
27
frontend/lib/service-definitions/example-prop-types.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const objectOfKeyValuesPropType = PropTypes.objectOf(PropTypes.string)
|
||||
.isRequired
|
||||
|
||||
const examplePropType = PropTypes.exact({
|
||||
title: PropTypes.string.isRequired,
|
||||
link: PropTypes.string,
|
||||
example: PropTypes.exact({
|
||||
pattern: PropTypes.string.isRequired,
|
||||
namedParams: objectOfKeyValuesPropType,
|
||||
queryParams: objectOfKeyValuesPropType,
|
||||
}).isRequired,
|
||||
preview: PropTypes.exact({
|
||||
label: PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
namedLogo: PropTypes.string,
|
||||
style: PropTypes.string,
|
||||
buildFromExample: PropTypes.bool,
|
||||
}),
|
||||
documentation: PropTypes.exact({
|
||||
__html: PropTypes.string.isRequired,
|
||||
}),
|
||||
})
|
||||
|
||||
export { examplePropType }
|
||||
@@ -49,6 +49,5 @@ const serviceDefinitionPropType = PropTypes.exact({
|
||||
export {
|
||||
arrayOfStringsPropType,
|
||||
objectOfKeyValuesPropType,
|
||||
examplePropType,
|
||||
serviceDefinitionPropType,
|
||||
}
|
||||
|
||||
@@ -63,29 +63,52 @@ describe('GitHub badge suggestions', function() {
|
||||
{
|
||||
title: 'GitHub issues',
|
||||
link: 'https://github.com/atom/atom/issues',
|
||||
path: '/github/issues/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/issues/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub forks',
|
||||
link: 'https://github.com/atom/atom/network',
|
||||
path: '/github/forks/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/forks/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub stars',
|
||||
link: 'https://github.com/atom/atom/stargazers',
|
||||
path: '/github/stars/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/stars/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub license',
|
||||
path: '/github/license/atom/atom',
|
||||
link: 'https://github.com/atom/atom/blob/master/LICENSE.md',
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
path: '/twitter/url/https/github.com/atom/atom',
|
||||
queryParams: {
|
||||
example: {
|
||||
pattern: '/twitter/url/:protocol(https|http)/:hostAndPath+',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'github.com/atom/atom',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
style: 'social',
|
||||
},
|
||||
},
|
||||
@@ -112,29 +135,52 @@ describe('GitHub badge suggestions', function() {
|
||||
{
|
||||
title: 'GitHub issues',
|
||||
link: 'https://github.com/badges/not-a-real-project/issues',
|
||||
path: '/github/issues/badges/not-a-real-project',
|
||||
example: {
|
||||
pattern: '/github/issues/:user/:repo',
|
||||
namedParams: { user: 'badges', repo: 'not-a-real-project' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub forks',
|
||||
link: 'https://github.com/badges/not-a-real-project/network',
|
||||
path: '/github/forks/badges/not-a-real-project',
|
||||
example: {
|
||||
pattern: '/github/forks/:user/:repo',
|
||||
namedParams: { user: 'badges', repo: 'not-a-real-project' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub stars',
|
||||
link: 'https://github.com/badges/not-a-real-project/stargazers',
|
||||
path: '/github/stars/badges/not-a-real-project',
|
||||
example: {
|
||||
pattern: '/github/stars/:user/:repo',
|
||||
namedParams: { user: 'badges', repo: 'not-a-real-project' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub license',
|
||||
path: '/github/license/badges/not-a-real-project',
|
||||
link: 'https://github.com/badges/not-a-real-project',
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user: 'badges', repo: 'not-a-real-project' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fbadges%2Fnot-a-real-project',
|
||||
path: '/twitter/url/https/github.com/badges/not-a-real-project',
|
||||
queryParams: {
|
||||
example: {
|
||||
pattern: '/twitter/url/:protocol(https|http)/:hostAndPath+',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'github.com/badges/not-a-real-project',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
style: 'social',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,14 @@ function twitterPage(url) {
|
||||
link: `https://twitter.com/intent/tweet?text=Wow:&url=${encodeURIComponent(
|
||||
url.href
|
||||
)}`,
|
||||
path: `/twitter/url/${schema}/${host}${path}`,
|
||||
queryParams: { style: 'social' },
|
||||
example: {
|
||||
pattern: '/twitter/url/:protocol(https|http)/:hostAndPath+',
|
||||
namedParams: { protocol: `${schema}`, hostAndPath: `${host}${path}` },
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
style: 'social',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +38,11 @@ function githubIssues(user, repo) {
|
||||
return {
|
||||
title: 'GitHub issues',
|
||||
link: `https://github.com/${repoSlug}/issues`,
|
||||
path: `/github/issues/${repoSlug}`,
|
||||
example: {
|
||||
pattern: '/github/issues/:user/:repo',
|
||||
namedParams: { user, repo },
|
||||
queryParams: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +51,11 @@ function githubForks(user, repo) {
|
||||
return {
|
||||
title: 'GitHub forks',
|
||||
link: `https://github.com/${repoSlug}/network`,
|
||||
path: `/github/forks/${repoSlug}`,
|
||||
example: {
|
||||
pattern: '/github/forks/:user/:repo',
|
||||
namedParams: { user, repo },
|
||||
queryParams: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +64,11 @@ function githubStars(user, repo) {
|
||||
return {
|
||||
title: 'GitHub stars',
|
||||
link: `https://github.com/${repoSlug}/stargazers`,
|
||||
path: `/github/stars/${repoSlug}`,
|
||||
example: {
|
||||
pattern: '/github/stars/:user/:repo',
|
||||
namedParams: { user, repo },
|
||||
queryParams: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +90,12 @@ async function githubLicense(githubApiProvider, user, repo) {
|
||||
|
||||
return {
|
||||
title: 'GitHub license',
|
||||
path: `/github/license/${repoSlug}`,
|
||||
link,
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user, repo },
|
||||
queryParams: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +123,14 @@ async function findSuggestions(githubApiProvider, url) {
|
||||
// end: function(json), with json of the form:
|
||||
// - suggestions: list of objects of the form:
|
||||
// - title: string
|
||||
// - link: target as a string URL.
|
||||
// - path: shields image URL path.
|
||||
// - queryParams: Object containing query params (Optional)
|
||||
// - link: target as a string URL
|
||||
// - example: object
|
||||
// - pattern: string
|
||||
// - namedParams: object
|
||||
// - queryParams: object (optional)
|
||||
// - link: target as a string URL
|
||||
// - preview: object (optional)
|
||||
// - style: string
|
||||
function setRoutes(allowedOrigin, githubApiProvider, server) {
|
||||
server.ajax.on('suggest/v1', (data, end, ask) => {
|
||||
// The typical dev and production setups are cross-origin. However, in
|
||||
|
||||
@@ -34,8 +34,12 @@ describe('Badge suggestions', function() {
|
||||
|
||||
expect(await githubLicense(apiProvider, 'atom', 'atom')).to.deep.equal({
|
||||
title: 'GitHub license',
|
||||
path: '/github/license/atom/atom',
|
||||
link: 'https://github.com/atom/atom/blob/master/LICENSE.md',
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
})
|
||||
|
||||
scope.done()
|
||||
@@ -52,8 +56,12 @@ describe('Badge suggestions', function() {
|
||||
|
||||
expect(await githubLicense(apiProvider, 'atom', 'atom')).to.deep.equal({
|
||||
title: 'GitHub license',
|
||||
path: '/github/license/atom/atom',
|
||||
link: 'https://github.com/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
})
|
||||
|
||||
scope.done()
|
||||
@@ -114,29 +122,52 @@ describe('Badge suggestions', function() {
|
||||
{
|
||||
title: 'GitHub issues',
|
||||
link: 'https://github.com/atom/atom/issues',
|
||||
path: '/github/issues/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/issues/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub forks',
|
||||
link: 'https://github.com/atom/atom/network',
|
||||
path: '/github/forks/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/forks/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub stars',
|
||||
link: 'https://github.com/atom/atom/stargazers',
|
||||
path: '/github/stars/atom/atom',
|
||||
example: {
|
||||
pattern: '/github/stars/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'GitHub license',
|
||||
path: '/github/license/atom/atom',
|
||||
link: 'https://github.com/atom/atom/blob/master/LICENSE.md',
|
||||
example: {
|
||||
pattern: '/github/license/:user/:repo',
|
||||
namedParams: { user: 'atom', repo: 'atom' },
|
||||
queryParams: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
link:
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
|
||||
path: '/twitter/url/https/github.com/atom/atom',
|
||||
queryParams: {
|
||||
example: {
|
||||
pattern: '/twitter/url/:protocol(https|http)/:hostAndPath+',
|
||||
namedParams: {
|
||||
protocol: 'https',
|
||||
hostAndPath: 'github.com/atom/atom',
|
||||
},
|
||||
queryParams: {},
|
||||
},
|
||||
preview: {
|
||||
style: 'social',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user