fleet/website/scripts/deliver-estimation-report.js
Eric 9965da174c
Website: add .retry() to Github API calls in estimation report script (#6132)
* website: add retry() to Github api calls in estimation script

* Update deliver-estimation-report.js
2022-06-09 00:08:13 -05:00

177 lines
9.5 KiB
JavaScript
Vendored

module.exports = {
friendlyName: 'Deliver estimation report',
description: 'Send estimation report to Slack.',
exits: {
success: {
description: 'It worked. The estimation report was sent to Slack.'
},
},
fn: async function () {
// ██████╗ ███████╗████████╗ ██████╗ ███████╗██████╗ ██████╗ ██████╗ ████████╗
// ██╔════╝ ██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝
// ██║ ███╗█████╗ ██║ ██████╔╝█████╗ ██████╔╝██║ ██║██████╔╝ ██║
// ██║ ██║██╔══╝ ██║ ██╔══██╗██╔══╝ ██╔═══╝ ██║ ██║██╔══██╗ ██║
// ╚██████╔╝███████╗ ██║ ██║ ██║███████╗██║ ╚██████╔╝██║ ██║ ██║
// ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
//
sails.log('Getting estimation report...');
if (!sails.config.custom.githubAccessToken) {
throw new Error('No GitHub access token configured! (Please set `sails.config.custom.githubAccessToken`.)');
}//•
let baseHeaders = {
'User-Agent': 'fleet story points',
'Authorization': `token ${sails.config.custom.githubAccessToken}`
};
let estimationReport = {};
// Fetch projects
let projects = await sails.helpers.http.get(`https://api.github.com/orgs/fleetdm/projects`, {}, baseHeaders).retry();// let projects = [];// « hack if you get rate limited and want to test beta projets
// This nasty little hack mixes in new "beta" projects that are part of Github Projects 2.0 (beta)
// but makes them look like normal projects from the actually-documented GitHub REST API.
// > [?] https://docs.github.com/en/enterprise-cloud@latest/issues/trying-out-the-new-projects-experience/using-the-api-to-manage-projects#finding-the-node-id-of-a-field
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// PS. In case you have to do anything with graphql ever again, try uncommenting this.
// ```
// console.log(
// require('util').inspect(
// await sails.helpers.http.post(`https://api.github.com/graphql`,{
// query:'{organization(login: "fleetdm") {projectsNext(first: 20) {nodes {id databaseId title fields(first: 20) {nodes {id name settings}} items(first: 20) {nodes{title id fieldValues(first: 8) {nodes{value projectField{name}}} content{...on Issue {repository{name} labels(first:20) {nodes{name}} assignees(first: 10) {nodes{login}}}}}} }}}}'
// }, baseHeaders),
// {depth:null}
// )
// );
// console.log();
// console.log();
// console.log('-0--------------');
// console.log();
// // return;
// ```
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
let graphqlHairball = await sails.helpers.http.post(`https://api.github.com/graphql`,{
query:'{organization(login: "fleetdm") {projectsNext(first: 20) {nodes {id databaseId title fields(first: 20) {nodes {id name settings}} items(first: 20) {nodes{title id fieldValues(first: 8) {nodes{value projectField{name}}} content{...on Issue {repository{name} labels(first:20) {nodes{name}} assignees(first: 10) {nodes{login}}}}}} }}}}'
}, baseHeaders).retry();
projects = projects.concat(
graphqlHairball.data.organization.projectsNext.nodes.map((betaProject) => ({
_isBetaProject: true,// « we need this because some APIs only work for one kind of project or the other
_betaProjectColumns: JSON.parse(_.find(betaProject.fields.nodes, {name: 'Status'}).settings).options.map((betaColumn) => ({
_isBetaColumn: true,
_betaStatusId: betaColumn.id,
name: betaColumn.name,
})),
_betaProjectCards: betaProject.items.nodes.filter((betaCard) => (
betaCard.content && betaCard.content.labels && betaCard.content.labels.nodes &&
betaCard.fieldValues && _.find(betaCard.fieldValues.nodes, (fieldValueNode) => fieldValueNode.projectField.name === 'Status')
)).map((betaCard) => ({
_isBetaCard: true,
_betaStatusId: _.find(betaCard.fieldValues.nodes, (fieldValueNode) => fieldValueNode.projectField.name === 'Status').value,
labels: betaCard.content.labels.nodes
})),
name: betaProject.title,// « it's been renamed for some reason
node_id: betaProject.id,// eslint-disable-line camelcase
id: betaProject.databaseId// « the good ole ID for the rest of us ("node_id" is the graphql ID)
}))
);// </hack>
// console.log(require('util').inspect(projects, {depth:null}));
// return;
await sails.helpers.flow.simultaneouslyForEach(projects, async(project)=>{
estimationReport[project.name] = {};
let columns;
if (!project._isBetaProject) {
columns = await sails.helpers.http.get(`https://api.github.com/projects/${project.id}/columns`, {}, baseHeaders).retry();
} else {
columns = project._betaProjectColumns;// [?] https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/objects#projectnextitem
}
await sails.helpers.flow.simultaneouslyForEach(columns, async(column)=>{
estimationReport[project.name][column.name] = 0;
let cards;
if (!project._isBetaProject) {
cards = await sails.helpers.http.get(`https://api.github.com/projects/columns/${column.id}/cards`, {}, baseHeaders).retry();
} else {
cards = project._betaProjectCards.filter((betaCard) => betaCard._betaStatusId === column._betaStatusId);
}
await sails.helpers.flow.simultaneouslyForEach(cards, async(card)=>{
// Get the number of story points associated with this card.
let numPoints = 0;
let labels;
if (!project._isBetaProject) {
if (!card.content_url) {
// ignore "notes" (FUTURE: Maybe add some kind of sniffing for a prefix like "[5]")
labels = [];
} else {
let issue = await sails.helpers.http.get(card.content_url, {}, baseHeaders).retry();
labels = issue.labels;
}
} else {
labels = card.labels;
}
let pointLabels = labels.filter((label)=> Number(label.name) >= 1 && Number(label.name) < Infinity);
if (pointLabels.length >= 2) { throw new Error(`Cannot have more than one story point label, but this card ${require('util').inspect(card, {depth:null})} seems to have more than one: ${_.pluck(pointLabels,'name')}`); }
if (pointLabels.length === 0) {
numPoints = 0;
} else {
numPoints = Number(pointLabels[0].name);
}
estimationReport[project.name][column.name] += numPoints;
});//∞
});//∞
});//∞
// ██████╗ ██████╗ ███████╗████████╗ ████████╗ ██████╗
// ██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝ ╚══██╔══╝██╔═══██╗
// ██████╔╝██║ ██║███████╗ ██║ ██║ ██║ ██║
// ██╔═══╝ ██║ ██║╚════██║ ██║ ██║ ██║ ██║
// ██║ ╚██████╔╝███████║ ██║ ██║ ╚██████╔╝
// ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝
//
// ███████╗██╗ █████╗ ██████╗██╗ ██╗
// ██╔════╝██║ ██╔══██╗██╔════╝██║ ██╔╝
// ███████╗██║ ███████║██║ █████╔╝
// ╚════██║██║ ██╔══██║██║ ██╔═██╗
// ███████║███████╗██║ ██║╚██████╗██║ ██╗
// ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
//
sails.log('Delivering estimation report to Slack...');
if (!sails.config.custom.slackWebhookUrlForGithubEstimates) {
throw new Error(
'Estimation report not delivered: slackWebhookUrlForGithubEstimates needs to be configured in sails.config.custom. Here\'s the undelivered report: ' +
`${require('util').inspect(estimationReport, {depth:null})}`
);
} else {
await sails.helpers.http.post(sails.config.custom.slackWebhookUrlForGithubEstimates, {
text: `New estimation report:\n${require('util').inspect(estimationReport, {depth:null})}`
}).retry();
}
}
};