Skip to content

Instantly share code, notes, and snippets.

@mLuby
Created June 15, 2017 03:03
Show Gist options
  • Save mLuby/4e3442308cd27f9f669ba18639562da2 to your computer and use it in GitHub Desktop.
Save mLuby/4e3442308cd27f9f669ba18639562da2 to your computer and use it in GitHub Desktop.
Determines average time for zenhub issue to be prioritized.
"use strict"
// CONFIG
const ZENHUB_TOKEN = "GET IT FROM ZENHUB"
const GITHUB_TOKEN= "GET IT FROM GITHUB"
const ORG_NAME = "GITHUB ORG NAME"
const REPO_NAME = "REPO NAME"
// IMPORTS
const req = require("request-promise-native")
const moment = require("moment")
// INIT
getRepoId()
.then(log)
.then(getPipelinesWithIssues)
.then(log)
.then(pipelinesToNonNewIssueNumbers)
.then(log)
.then(getEventsForIssueNumbers)
.then(log)
.then(selectTransfersFromNewIssues)
.then(log)
.then(getPrioritizations)
.then(log)
.then(prioritizationStats)
.then(log)
.catch(console.error)
// FUNCTIONS
function getRepoId () {
return req({
uri: `https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}`,
headers: { Authorization: `token ${GITHUB_TOKEN}`, "User-Agent": "myapp" }, json: true
}).then(repo => repo.id)
}
function getCollaboratorsById () {
return req({
uri: `https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}/collaborators`,
headers: { Authorization: `token ${GITHUB_TOKEN}`, "User-Agent": "myapp" }, json: true
}).then(collaborators => Promise.all(collaborators.map(collaborator => req({
uri: collaborator.url,
headers: { Authorization: `token ${GITHUB_TOKEN}`, "User-Agent": "myapp" }, json: true
}))))
.then(users => users.reduce((result, user) => (result[user.id] = `${user.name} <${user.login}>`, result), {}))
}
function getPipelinesWithIssues (repoId) {
return req({
uri: `https://api.zenhub.io/p1/repositories/${repoId}/board`,
headers: {'X-Authentication-Token': ZENHUB_TOKEN}, json: true
}).then(board => board.pipelines)
}
function pipelinesToNonNewIssueNumbers (pipelines) {
return pipelines.filter(pipeline => pipeline.name !== "New Issues")
.map(pipeline => pipeline.issues)
.map(issues => issues
.filter(issue => !issue.is_epic)
.map(issue => issue.issue_number)
)
.reduce(flatten)
}
function getEventsForIssueNumbers (issueNumbers) {
console.log(`Getting events for ${issueNumbers.length} issueNumbers…`)
return new Promise((resolve, reject) => {
// issue all requests
const successfulResults = []
const failedIssueNumbers = []
let retryAllowedTime = null
return getRepoId().then(repoId => {
issueNumbers.forEach(issueNumber => {
// TODO should get github issue data in parallel here…
return req({
uri: `https://api.zenhub.io/p1/repositories/${repoId}/issues/${issueNumber}/events`,
headers: {'X-Authentication-Token': ZENHUB_TOKEN}, json: true
})
.then(events => {
const eventsWithIssueNumber = events.map(event => Object.assign(event, {issueNumber}))
successfulResults.push(eventsWithIssueNumber)
retryAllowedTime = lastly(successfulResults, failedIssueNumbers, resolve, retryAllowedTime) // returns existing retryAllowedTime
})
.catch(error => {
failedIssueNumbers.push(issueNumber)
retryAllowedTime = lastly(successfulResults, failedIssueNumbers, resolve, retryAllowedTime, error) // returns latest wait time
})
})
function lastly (successfulResults, failedIssueNumbers, resolve, retryAllowedTime, error) {
const tmp = retryAllowedTime
retryAllowedTime = error && !isNaN(error.response.headers["x-ratelimit-reset"]) ? Number(error.response.headers["x-ratelimit-reset"]) * 1000 : retryAllowedTime // s -> ms
if (successfulResults.length + failedIssueNumbers.length === issueNumbers.length ) {
// when all requests have ended
if (successfulResults.length === issueNumbers.length) {
// if no failures, resolve with results
resolve(successfulResults)
} else {
// for failures
// figure out when to retry
const retryWaitMs = retryAllowedTime - Date.now()
// schedule retry and resolve successfulResults and results of the retry
console.log(`Waiting for ${retryWaitMs}ms (${moment.duration(retryWaitMs).humanize()}) to retry ${failedIssueNumbers.length} failedIssueNumbers… ${retryAllowedTime}`)
setTimeout(() => {
// console.log(`Wait elapsed! Retrying ${failedIssueNumbers.length} failedIssueNumbers…`)
getEventsForIssueNumbers(failedIssueNumbers)
.then(retriedSuccessfulResults => {
console.log("retriedSuccessfulResults", retriedSuccessfulResults)
const combinedResults = successfulResults.concat(retriedSuccessfulResults)
const filteredFlattenedResults = combinedResults.filter(a => a.length).reduce(flatten)
resolve(filteredFlattenedResults)//successfulResults.filter(a => a.length).concat(retriedSuccessfulResults.filter(a => a.length)))
}).catch(console.error)
}, retryWaitMs)
}
}
}
})
})
}
function selectTransfersFromNewIssues (events) {
return events.filter(isTransferFromNew)
}
function getPrioritizations (events) {
return getCollaboratorsById().then(githubIdToName => {
return Promise.all(events.map(event => {
return req({
uri: `https://api.github.com/repos/${ORG_NAME}/${REPO_NAME}/issues/${event.issueNumber}`,
headers: {
Authorization: `token ${GITHUB_TOKEN}`,
"User-Agent": "myapp"
},
json: true // Automatically parses the JSON string in the response
}).then(issue => ({
id: issue.number,
title: issue.title,
created_by: githubIdToName[issue.user.id] || issue.user.id,
created_at: issue.created_at,
prioritized_by: githubIdToName[event.user_id] || event.user_id,
prioritized_at: event.created_at,
prioritization_ms: (new Date(event.created_at)) - (new Date(issue.created_at)),
prioritization_human: moment.duration((new Date(event.created_at)) - (new Date(issue.created_at))).humanize()
}))
})).then(prioritizations => prioritizations.sort((a,b) => b.prioritization_ms - a.prioritization_ms))
})
}
function prioritizationStats (prioritizations) {
return {
totalPrioritizations: prioritizations.length,
averagePrioritization: moment.duration(prioritizations.map(p => p.prioritization_ms).reduce(sum)/prioritizations.length).humanize(),
averagePrioritizationMS: prioritizations.map(p => p.prioritization_ms).reduce(sum)/prioritizations.length
}
}
function log (x) {
console.log(x); return x
}
function flatten (list, item) {
return list.concat(item)
}
function isTransferFromNew (event) {
return event.type === "transferIssue" && event.from_pipeline.name === "New Issues"
}
function sum (sum, value) {
return sum + value
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment