Skip to content

Commit

Permalink
Merge branch 'master' into code-walkthrough
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmelnikow authored Jan 28, 2019
2 parents 3778745 + a9d6809 commit af9c07a
Show file tree
Hide file tree
Showing 22 changed files with 532 additions and 453 deletions.
1 change: 1 addition & 0 deletions lib/logos.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const logoAliases = {
azuredevops: 'azure-devops',
eclipse: 'eclipse-ide',
'gitter-white': 'gitter',
scrutinizer: 'scrutinizer-ci',
stackoverflow: 'stack-overflow',
tfs: 'azure-devops',
}
Expand Down
1 change: 0 additions & 1 deletion logo/scrutinizer.svg

This file was deleted.

2 changes: 1 addition & 1 deletion logo/superuser.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions logo/telegram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
138 changes: 74 additions & 64 deletions services/gem/gem-downloads.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ const Joi = require('joi')
const { downloadCount } = require('../../lib/color-formatters')
const { metric } = require('../../lib/text-formatters')
const { latest: latestVersion } = require('../../lib/version')
const { BaseJsonService, InvalidResponse } = require('..')
const { BaseJsonService, InvalidParameter, InvalidResponse } = require('..')
const { nonNegativeInteger } = require('../validators')

const keywords = ['ruby']

const gemsSchema = Joi.object({
const gemSchema = Joi.object({
downloads: nonNegativeInteger,
version_downloads: nonNegativeInteger,
}).required()

const versionsSchema = Joi.array()
const versionSchema = Joi.array()
.items(
Joi.object({
prerelease: Joi.boolean().required(),
Expand All @@ -27,76 +27,85 @@ const versionsSchema = Joi.array()
.required()

module.exports = class GemDownloads extends BaseJsonService {
async fetch({ repo, info }) {
const endpoint = info === 'dv' ? 'versions/' : 'gems/'
const schema = info === 'dv' ? versionsSchema : gemsSchema
const url = `https://rubygems.org/api/v1/${endpoint}${repo}.json`
return this._requestJson({
url,
schema,
async fetchDownloadCountForVersion({ gem, version }) {
const json = await this._requestJson({
url: `https://rubygems.org/api/v1/versions/${gem}.json`,
schema: versionSchema,
errorMessages: {
404: 'gem not found',
},
})

let wantedVersion
if (version === 'stable') {
wantedVersion = latestVersion(
json.filter(({ prerelease }) => !prerelease).map(({ number }) => number)
)
} else {
wantedVersion = version
}

const versionData = json.find(({ number }) => number === wantedVersion)
if (versionData) {
return versionData.downloads_count
} else {
throw new InvalidResponse({
prettyMessage: 'version not found',
})
}
}

async fetchDownloadCountForGem({ gem }) {
const {
downloads: totalDownloads,
version_downloads: versionDownloads,
} = await this._requestJson({
url: `https://rubygems.org/api/v1/gems/${gem}.json`,
schema: gemSchema,
errorMessages: {
404: 'gem not found',
},
})
return { totalDownloads, versionDownloads }
}

static render({ label, downloads }) {
static render({ which, version, downloads }) {
let label
if (version) {
label = `downloads@${version}`
} else if (which === 'dtv') {
label = 'downloads@latest'
}

return {
label,
message: metric(downloads),
color: downloadCount(downloads),
}
}

static _getLabel(version, info) {
if (version) {
return `downloads@${version}`
} else {
if (info === 'dtv') {
return 'downloads@latest'
} else {
return 'downloads'
}
}
}

async handle({ info, rubygem }) {
const splitRubygem = rubygem.split('/')
const repo = splitRubygem[0]
let version =
splitRubygem.length > 1 ? splitRubygem[splitRubygem.length - 1] : null
version = version === 'stable' ? version : semver.valid(version)
const label = this.constructor._getLabel(version, info)
const json = await this.fetch({ repo, info })

async handle({ which, gem, version }) {
let downloads
if (info === 'dt') {
downloads = json.downloads
} else if (info === 'dtv') {
downloads = json.version_downloads
} else if (info === 'dv') {
let versionData
if (version !== null && version === 'stable') {
const versions = json
.filter(ver => ver.prerelease === false)
.map(ver => ver.number)
// Found latest stable version.
const stableVersion = latestVersion(versions)
versionData = json.filter(ver => ver.number === stableVersion)[0]
downloads = versionData.downloads_count
} else if (version !== null) {
versionData = json.filter(ver => ver.number === version)[0]

downloads = versionData.downloads_count
} else {
throw new InvalidResponse({
underlyingError: new Error('version is null'),
if (which === 'dv') {
if (!version) {
throw new InvalidParameter({
prettyMessage: 'version downloads requires a version',
})
}
if (version !== 'stable' && !semver.valid(version)) {
throw new InvalidParameter({
prettyMessage: 'version should be "stable" or valid semver',
})
}
downloads = await this.fetchDownloadCountForVersion({ gem, version })
} else {
throw new InvalidResponse({
underlyingError: new Error('info is invalid'),
})
const {
totalDownloads,
versionDownloads,
} = await this.fetchDownloadCountForGem({ gem, which })
downloads = which === 'dtv' ? versionDownloads : totalDownloads
}

return this.constructor.render({ label, downloads })
return this.constructor.render({ which, version, downloads })
}

// Metadata
Expand All @@ -111,8 +120,7 @@ module.exports = class GemDownloads extends BaseJsonService {
static get route() {
return {
base: 'gem',
format: '(dt|dtv|dv)/(.+)',
capture: ['info', 'rubygem'],
pattern: ':which(dt|dtv|dv)/:gem/:version?',
}
}

Expand All @@ -126,7 +134,8 @@ module.exports = class GemDownloads extends BaseJsonService {
version: 'stable',
},
staticPreview: this.render({
label: this._getLabel('stable', 'dv'),
which: 'dv',
version: 'stable',
downloads: 70000,
}),
keywords,
Expand All @@ -139,7 +148,8 @@ module.exports = class GemDownloads extends BaseJsonService {
version: '4.1.0',
},
staticPreview: this.render({
label: this._getLabel('4.1.0', 'dv'),
which: 'dv',
version: '4.1.0',
downloads: 50000,
}),
keywords,
Expand All @@ -149,7 +159,7 @@ module.exports = class GemDownloads extends BaseJsonService {
pattern: 'dtv/:gem',
namedParams: { gem: 'rails' },
staticPreview: this.render({
label: this._getLabel(undefined, 'dtv'),
which: 'dtv',
downloads: 70000,
}),
keywords,
Expand All @@ -159,7 +169,7 @@ module.exports = class GemDownloads extends BaseJsonService {
pattern: 'dt/:gem',
namedParams: { gem: 'rails' },
staticPreview: this.render({
label: this._getLabel(undefined, 'dt'),
which: 'dt',
downloads: 900000,
}),
keywords,
Expand Down
26 changes: 18 additions & 8 deletions services/gem/gem-downloads.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const { isMetric } = require('../test-validators')

const t = (module.exports = require('..').createServiceTester())

// total downloads
t.create('total downloads (valid)')
.get('/dt/rails.json')
.expectJSONTypes(
Expand All @@ -17,9 +16,8 @@ t.create('total downloads (valid)')

t.create('total downloads (not found)')
.get('/dt/not-a-package.json')
.expectJSON({ name: 'downloads', value: 'not found' })
.expectJSON({ name: 'downloads', value: 'gem not found' })

// version downloads
t.create('version downloads (valid, stable version)')
.get('/dv/rails/stable.json')
.expectJSONTypes(
Expand All @@ -40,17 +38,29 @@ t.create('version downloads (valid, specific version)')

t.create('version downloads (package not found)')
.get('/dv/not-a-package/4.1.0.json')
.expectJSON({ name: 'downloads', value: 'not found' })
.expectJSON({ name: 'downloads', value: 'gem not found' })

t.create('version downloads (valid package, invalid version)')
.get('/dv/rails/not-a-version.json')
.expectJSON({ name: 'downloads', value: 'invalid' })
.expectJSON({
name: 'downloads',
value: 'version should be "stable" or valid semver',
})

t.create('version downloads (valid package, version not found)')
.get('/dv/rails/8.8.8.json')
.expectJSON({
name: 'downloads',
value: 'version not found',
})

t.create('version downloads (valid package, version not specified)')
.get('/dv/rails.json')
.expectJSON({ name: 'downloads', value: 'invalid' })
.expectJSON({
name: 'downloads',
value: 'version downloads requires a version',
})

// latest version downloads
t.create('latest version downloads (valid)')
.get('/dtv/rails.json')
.expectJSONTypes(
Expand All @@ -62,4 +72,4 @@ t.create('latest version downloads (valid)')

t.create('latest version downloads (not found)')
.get('/dtv/not-a-package.json')
.expectJSON({ name: 'downloads', value: 'not found' })
.expectJSON({ name: 'downloads', value: 'gem not found' })
81 changes: 26 additions & 55 deletions services/hackage/hackage-deps.service.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
'use strict'

const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const { checkErrorResponse } = require('../../lib/error-helper')
const { BaseService } = require('..')

// This legacy service should be rewritten to use e.g. BaseJsonService.
//
// Tips for rewriting:
// https://github.com/badges/shields/blob/master/doc/rewriting-services.md
//
// Do not base new services on this code.
module.exports = class HackageDeps extends LegacyService {
module.exports = class HackageDeps extends BaseService {
static get category() {
return 'dependencies'
}
Expand All @@ -22,62 +14,41 @@ module.exports = class HackageDeps extends LegacyService {
}
}

static get defaultBadgeData() {
return { label: 'dependencies' }
}

static get examples() {
return [
{
title: 'Hackage-Deps',
namedParams: { packageName: 'lens' },
staticPreview: {
label: 'dependencies',
message: 'up to date',
color: 'brightgreen',
},
staticPreview: this.render({ isOutdated: false }),
},
]
}

static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/hackage-deps\/v\/(.*)\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {
const repo = match[1] // eg, `lens`.
const format = match[2]
const reverseUrl = `http://packdeps.haskellers.com/licenses/${repo}`
const feedUrl = `http://packdeps.haskellers.com/feed/${repo}`
const badgeData = getBadgeData('dependencies', data)
static render({ isOutdated }) {
if (isOutdated) {
return { message: 'outdated', color: 'orange' }
} else {
return { message: 'up to date', color: 'brightgreen' }
}
}

async handle({ packageName }) {
const reverseUrl = `http://packdeps.haskellers.com/licenses/${packageName}`
const feedUrl = `http://packdeps.haskellers.com/feed/${packageName}`

// first call /reverse to check if the package exists
// this will throw a 404 if it doesn't
request(reverseUrl, (err, res, buffer) => {
if (checkErrorResponse(badgeData, err, res)) {
sendBadge(format, badgeData)
return
}
// first call /reverse to check if the package exists
// this will throw a 404 if it doesn't
await this._request({ url: reverseUrl })

// if the package exists, then query /feed to check the dependencies
request(feedUrl, (err, res, buffer) => {
if (err != null) {
badgeData.text[1] = 'inaccessible'
sendBadge(format, badgeData)
return
}
// if the package exists, then query /feed to check the dependencies
const { buffer } = await this._request({ url: feedUrl })

try {
const outdatedStr = `Outdated dependencies for ${repo} `
if (buffer.indexOf(outdatedStr) >= 0) {
badgeData.text[1] = 'outdated'
badgeData.colorscheme = 'orange'
} else {
badgeData.text[1] = 'up to date'
badgeData.colorscheme = 'brightgreen'
}
} catch (e) {
badgeData.text[1] = 'invalid'
}
sendBadge(format, badgeData)
})
})
})
)
const outdatedStr = `Outdated dependencies for ${packageName} `
const isOutdated = buffer.includes(outdatedStr)
return this.constructor.render({ isOutdated })
}
}
Loading

0 comments on commit af9c07a

Please sign in to comment.