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

Migrate [Jira] services to new service model #2541

Merged
merged 5 commits into from
Dec 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions services/jira/jira-base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict'

const BaseJsonService = require('../base-json')
const serverSecrets = require('../../lib/server-secrets')

module.exports = class JiraBase extends BaseJsonService {
static get category() {
return 'issue-tracking'
}

async fetch({ url, qs, schema, errorMessages }) {
const options = { qs }

if (serverSecrets && serverSecrets.jira_username) {
options.auth = {
user: serverSecrets.jira_username,
pass: serverSecrets.jira_password,
}
}

return this._requestJson({
schema,
url,
options,
errorMessages,
})
}
}
142 changes: 68 additions & 74 deletions services/jira/jira-issue.service.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,94 @@
'use strict'

const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const serverSecrets = require('../../lib/server-secrets')
const Joi = require('joi')
const JiraBase = require('./jira-base')

module.exports = class JiraIssue extends LegacyService {
static get category() {
return 'issue-tracking'
const schema = Joi.object({
fields: Joi.object({
status: Joi.object({
name: Joi.string().required(),
statusCategory: Joi.object({
colorName: Joi.string().required(),
}),
}).required(),
}).required(),
}).required()

module.exports = class JiraIssue extends JiraBase {
static render({ issueKey, statusName, statusColor }) {
let color = 'lightgrey'
if (statusColor) {
// map JIRA status color names to closest shields color schemes
const colorMap = {
'medium-gray': 'lightgrey',
green: 'green',
yellow: 'yellow',
brown: 'orange',
'warm-red': 'red',
'blue-gray': 'blue',
}
color = colorMap[statusColor]
}
return {
label: issueKey,
message: statusName,
color,
}
}

static get defaultBadgeData() {
return { color: 'lightgrey', label: 'jira' }
}

static get route() {
return {
base: 'jira/issue',
pattern: ':protocol(http|https)/:hostAndPath(.+)/:issueKey',
}
}

static get examples() {
return [
{
title: 'JIRA issue',
pattern: ':protocol/:hostAndPath+/:issueKey',
pattern: ':protocol/:hostAndPath/:issueKey',
namedParams: {
protocol: 'https',
hostAndPath: 'issues.apache.org/jira',
issueKey: 'KAFKA-2896',
},
staticPreview: {
label: 'kafka-2896',
message: 'Resolved',
color: 'green',
},
staticPreview: this.render({
issueKey: 'KAFKA-2896',
statusName: 'Resolved',
statusColor: 'green',
}),
keywords: ['jira', 'issue'],
},
]
}

static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/jira\/issue\/(http(?:s)?)\/(.+)\/([^/]+)\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {
const protocol = match[1] // eg, https
const host = match[2] // eg, issues.apache.org/jira
const issueKey = match[3] // eg, KAFKA-2896
const format = match[4]

const options = {
method: 'GET',
json: true,
uri: `${protocol}://${host}/rest/api/2/issue/${encodeURIComponent(
issueKey
)}`,
}
if (serverSecrets && serverSecrets.jira_username) {
options.auth = {
user: serverSecrets.jira_username,
pass: serverSecrets.jira_password,
}
}

// map JIRA color names to closest shields color schemes
const colorMap = {
'medium-gray': 'lightgrey',
green: 'green',
yellow: 'yellow',
brown: 'orange',
'warm-red': 'red',
'blue-gray': 'blue',
}

const badgeData = getBadgeData(issueKey, data)
request(options, (err, res, json) => {
if (err !== null) {
badgeData.text[1] = 'inaccessible'
sendBadge(format, badgeData)
return
}
try {
const jiraIssue = json
if (jiraIssue.fields && jiraIssue.fields.status) {
if (jiraIssue.fields.status.name) {
badgeData.text[1] = jiraIssue.fields.status.name // e.g. "In Development"
}
if (jiraIssue.fields.status.statusCategory) {
badgeData.colorscheme =
colorMap[jiraIssue.fields.status.statusCategory.colorName] ||
'lightgrey'
}
} else {
badgeData.text[1] = 'invalid'
}
sendBadge(format, badgeData)
} catch (e) {
badgeData.text[1] = 'invalid'
sendBadge(format, badgeData)
}
})
})
)
async handle({ protocol, hostAndPath, issueKey }) {
// Atlassian Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-api-2-issue-issueIdOrKey-get
const url = `${protocol}://${hostAndPath}/rest/api/2/issue/${encodeURIComponent(
issueKey
)}`
const json = await this.fetch({
url,
schema,
errorMessages: {
404: 'issue not found',
},
})
const issueStatus = json.fields.status
const statusName = issueStatus.name
let statusColor
if (issueStatus.statusCategory) {
calebcartwright marked this conversation as resolved.
Show resolved Hide resolved
statusColor = issueStatus.statusCategory.colorName
}
return this.constructor.render({
issueKey,
statusName,
statusColor,
})
}
}
95 changes: 95 additions & 0 deletions services/jira/jira-issue.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict'

const t = (module.exports = require('../create-service-tester')())
const jiraTestHelpers = require('./jira-test-helpers')

t.create('live: unknown issue')
.get('/https/issues.apache.org/jira/notArealIssue-000.json')
.expectJSON({ name: 'jira', value: 'issue not found' })

t.create('live: known issue')
.get('/https/issues.apache.org/jira/kafka-2896.json')
.expectJSON({ name: 'kafka-2896', value: 'Resolved' })

t.create('http endpoint')
.get('/http/issues.apache.org/jira/foo-123.json')
.intercept(nock =>
nock('http://issues.apache.org/jira/rest/api/2/issue')
.get(`/${encodeURIComponent('foo-123')}`)
.reply(200, {
fields: {
status: {
name: 'pending',
},
},
})
)
.expectJSON({ name: 'foo-123', value: 'pending' })

t.create('endpoint with port and path')
.get('/https/issues.apache.org:8000/jira/bar-345.json')
.intercept(nock =>
nock('https://issues.apache.org:8000/jira/rest/api/2/issue')
.get(`/${encodeURIComponent('bar-345')}`)
.reply(200, {
fields: {
status: {
name: 'done',
},
},
})
)
.expectJSON({ name: 'bar-345', value: 'done' })

t.create('endpoint with port and no path')
calebcartwright marked this conversation as resolved.
Show resolved Hide resolved
.get('/https/issues.apache.org:8080/abc-123.json')
.intercept(nock =>
nock('https://issues.apache.org:8080/rest/api/2/issue')
.get(`/${encodeURIComponent('abc-123')}`)
.reply(200, {
fields: {
status: {
name: 'under review',
},
},
})
)
.expectJSON({ name: 'abc-123', value: 'under review' })

t.create('endpoint with no port nor path')
.get('/https/issues.apache.org/test-001.json')
.intercept(nock =>
nock('https://issues.apache.org/rest/api/2/issue')
.get(`/${encodeURIComponent('test-001')}`)
.reply(200, {
fields: {
status: {
name: 'in progress',
},
},
})
)
.expectJSON({ name: 'test-001', value: 'in progress' })

t.create('with auth')
.before(jiraTestHelpers.mockJiraCreds)
.get('/https/myprivatejira.com/secure-234.json')
.intercept(nock =>
nock('https://myprivatejira.com/rest/api/2/issue')
.get(`/${encodeURIComponent('secure-234')}`)
// This ensures that the expected credentials from serverSecrets are actually being sent with the HTTP request.
// Without this the request wouldn't match and the test would fail.
.basicAuth({
Copy link
Member

Choose a reason for hiding this comment

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

Since this pattern is a bit unfamiliar, you could comment here that by including this here, it ensures the credentials are actually being sent out with the HTTP request. Otherwise the request wouldn't match and the test would fail.

Copy link
Member

Choose a reason for hiding this comment

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

Nice work on this test, by the way!

user: jiraTestHelpers.user,
pass: jiraTestHelpers.pass,
})
.reply(200, {
fields: {
status: {
name: 'in progress',
},
},
})
)
.finally(jiraTestHelpers.restore)
.expectJSON({ name: 'secure-234', value: 'in progress' })
Loading