Moved from Trello to GitHub porjects

This commit is contained in:
Jonathan Barrow
2022-08-07 04:17:53 -04:00
parent 50daf65329
commit 80bc0f01c0
11 changed files with 346 additions and 163 deletions

View File

@@ -1,98 +1,131 @@
const Trello = require('trello');
const { GraphQLClient, gql } = require('graphql-request');
const Stripe = require('stripe');
const got = require('got');
const config = require('../config.json');
const trello = new Trello(config.trello.api_key, config.trello.api_token);
const graphql = new GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: `bearer ${config.github.graphql_token}`,
}
});
const stripe = new Stripe(config.stripe.secret_key);
const VALID_LIST_NAMES = ['Not Started', 'Started', 'Completed'];
let trelloCache;
const getProjectCards = gql`
fragment ItemContent on Node {
__typename
... on DraftIssue {
title
}
... on Issue {
title
}
}
fragment Itemfields on Node {
__typename
... on ProjectV2ItemFieldSingleSelectValue {
name,
field {
... on ProjectV2SingleSelectField {
name
}
}
}
}
query getProjectCards($orgName: String!) {
organization(login: $orgName) {
projectsV2(first: 100) {
nodes {
repositories(first: 1) {
nodes {
url
}
}
title
items(first: 100) {
nodes {
id
content {
...ItemContent
}
fieldValues(first: 20) {
nodes {
...Itemfields
}
}
}
}
}
}
}
}
`;
let githubProjectsCache;
let stripeDonationCache;
async function getTrelloCache() {
const available = await trelloAPIAvailable();
if (!available) {
return trelloCache || {
update_time: 0,
sections: [{
title: 'Upstream API error',
id: '',
percentage_complete: '',
progress: {
not_started: [ 'Trello API unavailable' ],
started: [],
completed: []
}
}],
};
async function getGithubProjectsCache() {
if (!githubProjectsCache) {
githubProjectsCache = await updateGithubProjectsCache();
}
if (!trelloCache) {
trelloCache = await updateTrelloCache();
if (githubProjectsCache.update_time < Date.now() - (1000 * 60 * 60)) {
githubProjectsCache = await updateGithubProjectsCache();
}
if (trelloCache.update_time < Date.now() - (1000 * 60 * 60)) {
trelloCache = await updateTrelloCache();
}
return trelloCache;
return githubProjectsCache;
}
async function updateTrelloCache() {
const progressCache = {
function getProgressField(fields) {
const found = fields.nodes.find(v => v.field?.name === 'Status');
return found?.name ?? undefined;
}
async function updateGithubProjectsCache() {
const projectsCacheData = {
update_time: Date.now(),
sections: []
};
const boards = await trello.getOrgBoards(config.trello.board_name);
const data = await graphql.request(getProjectCards, {
orgName: 'PretendoNetwork',
});
for (const board of boards) {
const meta = {
title: '',
id: '',
percentage_complete: 0,
progress: {
not_started: [],
started: [],
completed: []
}
};
const projects = data.organization.projectsV2.nodes;
meta.title = board.name;
meta.id = board.shortLink;
const lists = await trello.getListsOnBoard(board.id);
const listNames = lists.map(list => list.name);
const hasAllValidLists = listNames.every(name => VALID_LIST_NAMES.includes(name));
if (!hasAllValidLists) {
for (const project of projects) {
if (!project.repositories.nodes[0]) {
continue;
}
const cards = await trello.getCardsOnBoard(board.id);
for (const card of cards) {
const cardList = lists.find(({ id }) => id === card.idList);
const listName = cardList.name.toLowerCase().replace(' ', '_');
if (meta.progress[listName]) {
meta.progress[listName].push(card.name);
const extractedData = {
title: project.title,
url: project.repositories.nodes[0]?.url,
cards: {
done: [],
in_progress: [],
todo: []
}
};
for (const { content, fieldValues } of project.items.nodes) {
const progress = getProgressField(fieldValues);
if (!['DraftIssue'].includes(content.__typename)) {
continue; // not a supported card, skip
}
if (!progress) {
continue; // entry does not have a status, skip
}
extractedData.cards[progress.toLowerCase().replace(' ', '_')]?.push(content.title);
}
if (meta.progress.not_started.length !== 0 || meta.progress.started.length !== 0 || meta.progress.completed.length !== 0) {
progressCache.sections.push(meta);
}
projectsCacheData.sections.push(extractedData);
}
return progressCache;
}
async function trelloAPIAvailable() {
const { status } = await got('https://trello.status.atlassian.com/api/v2/status.json').json();
return status.indicator !== 'major' && status.indicator !== 'critical';
return projectsCacheData;
}
async function getStripeDonationCache() {
@@ -147,6 +180,6 @@ async function updateStripeDonationCache() {
}
module.exports = {
getTrelloCache,
getGithubProjectsCache,
getStripeDonationCache
};

View File

@@ -40,4 +40,4 @@ async function renderDataMiddleware(request, response, next) {
}
}
module.exports = renderDataMiddleware;
module.exports = renderDataMiddleware;

View File

@@ -1,16 +1,12 @@
const { Router } = require('express');
const { boards } = require('../../boards/boards.json');
const router = new Router();
const { getTrelloCache } = require('../cache');
const { getGithubProjectsCache } = require('../cache');
router.get('/', async (request, response) => {
const renderData = {};
const renderData = {
boards
};
const cache = await getTrelloCache();
const githubProjectsCache = await getGithubProjectsCache();
// Builds the arrays of people for the special thanks section
@@ -43,37 +39,37 @@ router.get('/', async (request, response) => {
percentageSum: 0
},
percent: 0,
progress: {
not_started: [],
started: [],
completed: []
cards: {
todo: [],
in_progress: [],
done: []
}
};
// Calculates individual completion percentages and progress states
cache.sections.forEach(section => {
const { not_started, started, completed } = section.progress;
githubProjectsCache.sections.forEach(section => {
const { todo, in_progress, done } = section.cards;
// Calculates the completion percentage of the project, and sums it to the total
const sectionCompletionPercentage = (completed.length + started.length * 0.5) / (completed.length + started.length + not_started.length);
const sectionCompletionPercentage = ((done.length + in_progress.length * 0.5) / (done.length + in_progress.length + todo.length)) || 0;
totalProgress._calc.percentageSum += sectionCompletionPercentage;
const sectionTitle = `${section.title} [${Math.round(sectionCompletionPercentage * 100)}%]`;
if (completed !== [] && started + not_started === []) {
// if all the section tasks have been completed, push to completed
totalProgress.progress.completed.push(sectionTitle);
} else if (not_started !== [] && started + completed === []) {
// if none the section tasks have been completed or started, push to not started
totalProgress.progress.not_started.push(sectionTitle);
if (done !== [] && in_progress + todo === []) {
// if all the section tasks have been done, push to done
totalProgress.cards.done.push(sectionTitle);
} else if (todo !== [] && in_progress + done === []) {
// if none the section tasks have been done or in_progress, push to todo
totalProgress.cards.todo.push(sectionTitle);
} else {
// for all other combos, push to started
totalProgress.progress.started.push(sectionTitle);
// for all other combos, push to in_progress
totalProgress.cards.in_progress.push(sectionTitle);
}
});
// Calculates global completion percentage
totalProgress.percent = Math.round(totalProgress._calc.percentageSum / cache.sections.length * 100);
totalProgress.percent = Math.round(totalProgress._calc.percentageSum / githubProjectsCache.sections.length * 100);
renderData.featuredFeatureList = totalProgress;

View File

@@ -1,19 +1,14 @@
const { Router } = require('express');
const { boards } = require('../../boards/boards.json');
const router = new Router();
const { getTrelloCache, getStripeDonationCache } = require('../cache');
const { getGithubProjectsCache, getStripeDonationCache } = require('../cache');
router.get('/', async (request, response) => {
const renderData = {
boards
progressLists: await getGithubProjectsCache(),
donationCache: await getStripeDonationCache()
};
const trelloCache = await getTrelloCache();
renderData.progressLists = trelloCache;
const stripeDonationCache = await getStripeDonationCache();
renderData.donationCache = stripeDonationCache;
response.render('progress', renderData);
});