mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Make estimation script work with GitHub projects beta (#4249)
* got projects 2.0 showing up * cleanup graphql resentment leftovers * Implement annoying shim for graphql. * got columns working * made it all work * make graphql-related variable names slightly more positive * Get ready to rename this sucker * Rename script * a touch of cleanup
This commit is contained in:
parent
fbfc77150f
commit
f1c0158149
2 changed files with 177 additions and 74 deletions
177
website/scripts/deliver-estimation-report.js
vendored
Normal file
177
website/scripts/deliver-estimation-report.js
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
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);// 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);
|
||||
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);
|
||||
} 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);
|
||||
} 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);
|
||||
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.slackWebhookUrlForContactForm) {
|
||||
throw new Error(
|
||||
'Estimation report not delivered: slackWebhookUrlForContactForm needs to be configured in sails.config.custom. Here\'s the undelivered report: ' +
|
||||
`${require('util').inspect(estimationReport, {depth:null})}`
|
||||
);
|
||||
} else {
|
||||
// TODO: instead of just copying the contact form handler webhook URL, make one that goes to an appropriate channel
|
||||
await sails.helpers.http.post(sails.config.custom.slackWebhookUrlForContactForm, {
|
||||
text: `New estimation report:\n${require('util').inspect(estimationReport, {depth:null})}`
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
74
website/scripts/get-estimation-report.js
vendored
74
website/scripts/get-estimation-report.js
vendored
|
|
@ -1,74 +0,0 @@
|
|||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Get estimation report',
|
||||
|
||||
|
||||
description: '',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
outputFriendlyName: 'Estimation report',
|
||||
outputType: {}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
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 = {};
|
||||
|
||||
// FUTURE: only look at particular projects here instead of all of them
|
||||
let projects = await sails.helpers.http.get(`https://api.github.com/orgs/fleetdm/projects`, {}, baseHeaders);
|
||||
// projects = projects.filter((project)=> Number(project.id) === 13160610);// « hack to try it w/ just one project
|
||||
|
||||
await sails.helpers.flow.simultaneouslyForEach(projects, async(project)=>{
|
||||
estimationReport[project.name] = {};
|
||||
let columns = await sails.helpers.http.get(`https://api.github.com/projects/${project.id}/columns`, {}, baseHeaders);
|
||||
await sails.helpers.flow.simultaneouslyForEach(columns, async(column)=>{
|
||||
// console.log('------',project.name, column.name);
|
||||
estimationReport[project.name][column.name] = 0;
|
||||
let cards = await sails.helpers.http.get(`https://api.github.com/projects/columns/${column.id}/cards`, {}, baseHeaders);
|
||||
await sails.helpers.flow.simultaneouslyForEach(cards, async(card)=>{
|
||||
|
||||
// Get the number of story points associated with this card.
|
||||
let numPoints = 0;
|
||||
if (!card.content_url) {
|
||||
// ignore "notes" (FUTURE: Maybe add some kind of sniffing for a prefix like "[5]")
|
||||
} else {
|
||||
let issue = await sails.helpers.http.get(card.content_url, {}, baseHeaders);
|
||||
let pointLabels = issue.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 issue #${issue.id} seems to have more than one: ${_.pluck(pointLabels,'name')}`); }
|
||||
if (pointLabels.length === 0) {
|
||||
numPoints = 0;
|
||||
} else {
|
||||
numPoints = Number(pointLabels[0].name);
|
||||
}
|
||||
}
|
||||
// console.log(`${column.name} :: ${card.id} :: +${numPoints}`);
|
||||
estimationReport[project.name][column.name] += numPoints;
|
||||
});//∞
|
||||
});//∞
|
||||
});//∞
|
||||
|
||||
return estimationReport;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
Loading…
Reference in a new issue