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

Move [StaticBadge] to own service & add test; also affects [gitter] #2284

Merged
merged 36 commits into from
Nov 17, 2018
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b2d9016
move static badge to own service & add test
RedSparr0w Jul 23, 2018
c1c2ce0
[eslint]
RedSparr0w Jul 23, 2018
612ac05
add category
RedSparr0w Jul 23, 2018
5abac51
fix test
RedSparr0w Jul 23, 2018
b85a380
add coalesce function, support empty label & message
RedSparr0w Jul 24, 2018
3b3bc36
update test
RedSparr0w Jul 24, 2018
3613b58
Merge branch 'master' into static-service
RedSparr0w Jul 24, 2018
64db5b1
update category
RedSparr0w Jul 24, 2018
9af7333
Merge branch 'master' into static-service
RedSparr0w Aug 2, 2018
58c67f3
Merge branch 'master' into static-service
paulmelnikow Nov 7, 2018
ce5ded3
Clean lint
paulmelnikow Nov 7, 2018
8303a3a
Run prettier
paulmelnikow Nov 7, 2018
882e42c
Fixes
paulmelnikow Nov 7, 2018
d166540
Restore old cache behavior + apply to other static badge
paulmelnikow Nov 7, 2018
df2feec
Tidy up service tester
paulmelnikow Nov 7, 2018
c6e0115
Rework this again
paulmelnikow Nov 7, 2018
8d7b638
Formatting
paulmelnikow Nov 7, 2018
a19fcff
Fixes
paulmelnikow Nov 7, 2018
7656ba3
Use a subclass
paulmelnikow Nov 7, 2018
964b7d5
Clean diff
paulmelnikow Nov 7, 2018
bc26948
Clean lint
paulmelnikow Nov 7, 2018
76c22b2
Clean diff
paulmelnikow Nov 7, 2018
a4b7241
+ Inline comment
paulmelnikow Nov 7, 2018
d574f24
Clean diff
paulmelnikow Nov 7, 2018
1e2a602
+ analytics
paulmelnikow Nov 7, 2018
30f0116
Merge branch 'master' into static-service
paulmelnikow Nov 8, 2018
906a281
format -> pattern
paulmelnikow Nov 9, 2018
a5f6bce
Merge branch 'master' into static-service
paulmelnikow Nov 9, 2018
9d4fe92
url -> route
paulmelnikow Nov 9, 2018
aab5906
Merge branch 'master' into static-service
paulmelnikow Nov 11, 2018
a4ae95d
Merge branch 'master' into static-service
paulmelnikow Nov 14, 2018
704d231
Merge branch 'master' into static-service
paulmelnikow Nov 16, 2018
1144f00
Update based on lookup table measurement
paulmelnikow Nov 16, 2018
2731086
Update based on gh-badges move
paulmelnikow Nov 16, 2018
9a36161
Fix all one color badge
paulmelnikow Nov 16, 2018
d7c5335
Merge branch 'master' into static-service
paulmelnikow Nov 17, 2018
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
10 changes: 5 additions & 5 deletions lib/request-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ function getBadgeMaxAge(handlerOptions, queryParams) {
? parseInt(process.env.BADGE_MAX_AGE_SECONDS)
: 120
if (handlerOptions.cacheLength) {
// if we've set a more specific cache length for this badge (or category),
// use that instead of env.BADGE_MAX_AGE_SECONDS
maxAge = parseInt(handlerOptions.cacheLength)
// If we've set a more specific cache length for this badge (or category),
// use that instead of env.BADGE_MAX_AGE_SECONDS.
maxAge = handlerOptions.cacheLength
Copy link
Member Author

Choose a reason for hiding this comment

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

Technically unrelated, as in the current implementation the static badge does not call this function.

There's no need for parseInt since we're passing numbers, not strings.

}
if (isInt(queryParams.maxAge) && parseInt(queryParams.maxAge) > maxAge) {
// only allow queryParams.maxAge to override the default
// if it is greater than the default
// Only allow queryParams.maxAge to override the default if it is greater
// than the default.
maxAge = parseInt(queryParams.maxAge)
}
return maxAge
Expand Down
57 changes: 5 additions & 52 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ const { makeMakeBadgeFn } = require('./lib/make-badge')
const { QuickTextMeasurer } = require('./lib/text-measurer')
const suggest = require('./lib/suggest')
const {
makeColorB,
makeLabel: getLabel,
makeBadgeData: getBadgeData,
setBadgeColor,
} = require('./lib/badge-data')
Expand All @@ -34,7 +32,6 @@ const {
} = require('./lib/request-handler')
const { clearRegularUpdateCache } = require('./lib/regular-update')
const { makeSend } = require('./lib/result-sender')
const { escapeFormat } = require('./lib/path-helpers')

const serverStartTime = new Date(new Date().toGMTString())

Expand Down Expand Up @@ -117,8 +114,11 @@ camp.notfound(/.*/, (query, match, end, request) => {

loadServiceClasses().forEach(serviceClass =>
serviceClass.register(
{ camp, handleRequest: cache, githubApiProvider },
{ handleInternalErrors: config.handleInternalErrors }
{ camp, measurer, handleRequest: cache, githubApiProvider },
{
handleInternalErrors: config.handleInternalErrors,
profiling: config.profiling,
}
)
)

Expand Down Expand Up @@ -238,53 +238,6 @@ camp.route(
})
)

// Any badge.
camp.route(
/^\/(:|badge\/)(([^-]|--)*?)-?(([^-]|--)*)-(([^-]|--)+)\.(svg|png|gif|jpg)$/,
(data, match, end, ask) => {
const subject = escapeFormat(match[2])
const status = escapeFormat(match[4])
const color = escapeFormat(match[6])
const format = match[8]

analytics.noteRequest(data, match)

// Cache management - the badge is constant.
const cacheDuration = (3600 * 24 * 1) | 0 // 1 day.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
if (+new Date(ask.req.headers['if-modified-since']) >= +serverStartTime) {
ask.res.statusCode = 304
ask.res.end() // not modified.
return
}
ask.res.setHeader('Last-Modified', serverStartTime.toGMTString())

// Badge creation.
try {
const badgeData = getBadgeData(subject, data)
badgeData.text[0] = getLabel(undefined, { label: subject })
badgeData.text[1] = status
badgeData.colorB = makeColorB(color, data)
badgeData.template = data.style
if (config.profiling.makeBadge) {
console.time('makeBadge total')
}
const svg = makeBadge(badgeData)
if (config.profiling.makeBadge) {
console.timeEnd('makeBadge total')
}
makeSend(format, ask.res, end)(svg)
} catch (e) {
log.error(e.stack)
const svg = makeBadge({
text: ['error', 'bad badge'],
colorscheme: 'red',
})
makeSend(format, ask.res, end)(svg)
}
}
)

// Production cache debugging.
let bitFlip = false
camp.route(/^\/flip\.svg$/, (data, match, end, ask) => {
Expand Down
60 changes: 60 additions & 0 deletions services/base-static.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict'

const { makeMakeBadgeFn } = require('../lib/make-badge')
const { makeSend } = require('../lib/result-sender')
const analytics = require('../lib/analytics')
const BaseService = require('./base')

const serverStartTime = new Date(new Date().toGMTString())
Copy link
Member Author

Choose a reason for hiding this comment

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

There's no point in passing this down from server.js.


module.exports = class BaseStaticService extends BaseService {
// Note: Since this is a static service, it is not `async`.
handle(namedParams, queryParams) {
throw new Error(`Handler not implemented for ${this.constructor.name}`)
}

static register({ camp, measurer }, serviceConfig) {
const makeBadge = makeMakeBadgeFn(measurer)

camp.route(this._regex, (queryParams, match, end, ask) => {
analytics.noteRequest(queryParams, match)

if (+new Date(ask.req.headers['if-modified-since']) >= +serverStartTime) {
// Send Not Modified.
ask.res.statusCode = 304
ask.res.end()
return
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Currently this behavior is not tested.


const serviceInstance = new this({}, serviceConfig)
const namedParams = this._namedParamsForMatch(match)
let serviceData
try {
// Note: no `await`.
serviceData = serviceInstance.handle(namedParams, queryParams)
} catch (error) {
serviceData = serviceInstance._handleError(error)
}

const badgeData = this._makeBadgeData(queryParams, serviceData)

// The final capture group is the extension.
const format = match.slice(-1)[0]
badgeData.format = format

if (serviceConfig.profiling.makeBadge) {
console.time('makeBadge total')
}
const svg = makeBadge(badgeData)
if (serviceConfig.profiling.makeBadge) {
console.timeEnd('makeBadge total')
}

const cacheDuration = 3600 * 24 * 1 // 1 day.
ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`)
ask.res.setHeader('Last-Modified', serverStartTime.toGMTString())
Copy link
Member Author

Choose a reason for hiding this comment

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

Currently this behavior is not tested.


makeSend(format, ask.res, end)(svg)
})
}
}
108 changes: 58 additions & 50 deletions services/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const {
const { staticBadgeUrl } = require('../lib/make-badge-url')
const trace = require('./trace')

function coalesce(...candidates) {
return candidates.find(c => typeof c === 'string')
}

class BaseService {
constructor({ sendAndCacheRequest }, { handleInternalErrors }) {
this._requestFetcher = sendAndCacheRequest
Expand Down Expand Up @@ -248,6 +252,52 @@ class BaseService {
return result
}

_handleError(error) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Extract into a method so BaseStaticService can invoke it from overridden register().

if (error instanceof NotFound || error instanceof InvalidParameter) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'red',
}
} else if (
error instanceof InvalidResponse ||
error instanceof Inaccessible ||
error instanceof Deprecated
) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'lightgray',
}
} else if (this._handleInternalErrors) {
if (
!trace.logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
) {
// This is where we end up if an unhandled exception is thrown in
// production. Send the error to the logs.
console.log(error)
}
return {
label: 'shields',
message: 'internal error',
color: 'lightgray',
}
} else {
trace.logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
throw error
}
}

async invokeHandler(namedParams, queryParams) {
trace.logTrace(
'inbound',
Expand All @@ -260,49 +310,7 @@ class BaseService {
try {
return await this.handle(namedParams, queryParams)
} catch (error) {
if (error instanceof NotFound || error instanceof InvalidParameter) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'red',
}
} else if (
error instanceof InvalidResponse ||
error instanceof Inaccessible ||
error instanceof Deprecated
) {
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
return {
message: error.prettyMessage,
color: 'lightgray',
}
} else if (this._handleInternalErrors) {
if (
!trace.logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
) {
// This is where we end up if an unhandled exception is thrown in
// production. Send the error to the logs.
console.log(error)
}
return {
label: 'shields',
message: 'internal error',
color: 'lightgray',
}
} else {
trace.logTrace(
'unhandledError',
emojic.boom,
'Unhandled internal error',
error
)
throw error
}
return this._handleError(error)
}
}

Expand Down Expand Up @@ -333,8 +341,10 @@ class BaseService {

const badgeData = {
text: [
overrideLabel || serviceLabel || defaultLabel || this.category,
serviceMessage || 'n/a',
// Use `coalesce()` to support empty labels and messages, as in the
// static badge.
coalesce(overrideLabel, serviceLabel, defaultLabel, this.category),
coalesce(serviceMessage, 'n/a'),
Copy link
Member Author

Choose a reason for hiding this comment

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

This is covered by the static badge tests @RedSparr0w wrote.

],
template: style,
logo: makeLogo(style === 'social' ? defaultLogo : undefined, {
Expand All @@ -352,30 +362,28 @@ class BaseService {
}

static register({ camp, handleRequest, githubApiProvider }, serviceConfig) {
const ServiceClass = this // In a static context, "this" is the class.
Copy link
Member Author

Choose a reason for hiding this comment

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

Some references use this and some use ServiceClass. Don't really care which we use, though consistent = better.


camp.route(
this._regex,
handleRequest({
queryParams: this.route.queryParams,
handler: async (queryParams, match, sendBadge, request) => {
const namedParams = this._namedParamsForMatch(match)
const serviceInstance = new ServiceClass(
const serviceInstance = new this(
{
sendAndCacheRequest: request.asPromise,
sendAndCacheRequestWithCallbacks: request,
githubApiProvider,
},
serviceConfig
)
const namedParams = this._namedParamsForMatch(match)
const serviceData = await serviceInstance.invokeHandler(
namedParams,
queryParams
)
trace.logTrace('outbound', emojic.shield, 'Service data', serviceData)
const badgeData = this._makeBadgeData(queryParams, serviceData)

// Assumes the final capture group is the extension
// The final capture group is the extension.
const format = match.slice(-1)[0]
sendBadge(format, badgeData)
},
Expand Down
35 changes: 18 additions & 17 deletions services/gitter/gitter.service.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
'use strict'

const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const BaseStaticService = require('../base-static')

module.exports = class Gitter extends LegacyService {
module.exports = class Gitter extends BaseStaticService {
static get category() {
return 'chat'
}

static get route() {
return { base: 'gitter/room' }
return {
base: 'gitter/room',
pattern: ':user/:repo',
}
}

static get examples() {
return [
{
title: 'Gitter',
previewUrl: 'nwjs/nw.js',
urlPattern: ':user/:repo',
staticExample: this.render(),
exampleUrl: 'nwjs/nw.js',
},
]
}

static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/gitter\/room\/([^/]+\/[^/]+)\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {
// match[1] is the repo, which is not used.
const format = match[2]
static get defaultBadgeData() {
return { label: 'chat' }
}

static render() {
return { message: 'on gitter', color: 'brightgreen' }
}

const badgeData = getBadgeData('chat', data)
badgeData.text[1] = 'on gitter'
badgeData.colorscheme = 'brightgreen'
sendBadge(format, badgeData)
})
)
handle() {
return this.constructor.render()
}
}
Loading