Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ async function main({ g, c }) {
const filepath = 'github-actions/utils/_data/inactive-members.json';
const rawData = fs.readFileSync(filepath, 'utf8');
let inactiveLists = JSON.parse(rawData);
const inactiveWithOpen = parseInactiveOpen(inactiveLists['cannotRemoveYet']);
const inactiveWithOpen = parseInactiveOpen(inactiveLists['inactiveOpenIssue']);
const nonTeamWithOpen = parseNonTeamOpen(inactiveLists['nonTeamOpenIssue']);

const owner = context.repo.owner;
const repo = context.repo.repo;
Expand All @@ -33,7 +34,7 @@ async function main({ g, c }) {
const issueNumber = issue.number;

// Add issue number used to reference the issue and comment on the `Dev/PM Agenda and Notes`
const commentBody = `**Review Inactive Team Members:** #` + issueNumber + inactiveWithOpen;
const commentBody = `**Review Inactive Team Members:** #` + issueNumber + inactiveWithOpen + nonTeamWithOpen;
await postComment(AGENDA_ISSUE_NUM, commentBody, github, context);
}

Expand Down Expand Up @@ -84,15 +85,31 @@ const createIssue = async (owner, repo, inactiveLists) => {
};

const parseInactiveOpen = (inactiveOpens) => {
if(Object.keys(inactiveOpens).length === 0){
if (Object.keys(inactiveOpens).length === 0) {
return '';
} else {
let inactiveOpen = '\r\n\nInactive members with open issues:\r\n';
for(const [key, value] of Object.entries(inactiveOpens)){
inactiveOpen += ' - ' + key + ': #' + value + '\r\n';
for (const [user, issues] of Object.entries(inactiveOpens)) {
for (const issue of issues) {
inactiveOpen += ` - ${user}: #${issue}\r\n`;
}
}
return inactiveOpen;
}
};

const parseNonTeamOpen = (nonTeamOpens) => {
if (Object.keys(nonTeamOpens).length === 0) {
return '';
} else {
let nonTeamOpen = '\r\n\nNon-team members with open issues:\r\n';
for (const [user, issues] of Object.entries(nonTeamOpens)) {
for (const issue of issues) {
nonTeamOpen += ` - ${user}: #${issue}\r\n`;
}
}
return nonTeamOpen;
}
};

module.exports = main;
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,25 @@ let dates = [oneMonthAgo, twoMonthsAgo];

/**
* Main function
* @param {Object} g - github object from actions/github-script
* @param {Object} g - GitHub object from actions/github-script
* @param {Object} c - context object from actions/github-script
* @return {Object} results - object to use in `trim-inactive-members.js`
*/
async function main({ g, c }) {
github = g;
context = c;

const [contributorsOneMonthAgo, contributorsTwoMonthsAgo, inactiveWithOpenIssue] = await fetchContributors(dates);
const [contributorsOneMonthAgo, contributorsTwoMonthsAgo, inactiveWithOpenSkills,inactiveWithOpenIssue] = await fetchContributors(dates);
console.log(`-`.repeat(60));
console.log('List of active contributors since ' + dates[0].slice(0, 10) + ':');
console.log(contributorsOneMonthAgo);

return {
recentContributors: contributorsOneMonthAgo,
previousContributors: contributorsTwoMonthsAgo,
inactiveWithOpenIssue: inactiveWithOpenIssue,
dates: dates,
inactiveWithOpenSkills,
inactiveWithOpenIssue,
dates,
};
};

Expand All @@ -58,6 +59,7 @@ async function main({ g, c }) {
async function fetchContributors(dates){
let allContributorsSinceOneMonthAgo = {};
let allContributorsSinceTwoMonthsAgo = {};
let inactiveWithOpenSkills = {};
let inactiveWithOpenIssue = {};

// Members of 'website-maintain' team are considered permanent members
Expand All @@ -76,7 +78,7 @@ async function fetchContributors(dates){
let pageNum = 1;
let result = [];

// Since Github only allows to fetch max 100 items per request, we need to 'flip' pages
// Since GitHub only allows to fetch max 100 items per request, we need to 'flip' pages
while(true){
// Fetch 100 items per each page (`pageNum`)
const contributors = await github.request(api, {
Expand Down Expand Up @@ -128,9 +130,9 @@ async function fetchContributors(dates){
else if (date === dates[1]) {
const regex = /Pre-work checklist|Skills Issue/i;
if (regex.test(contributorInfo.title)) {
inactiveWithOpenIssue[assignee] = [issueNum, true];
inactiveWithOpenSkills[assignee] = [issueNum];
} else {
inactiveWithOpenIssue[assignee] = [issueNum, false];
inactiveWithOpenIssue[assignee] = [issueNum];
}
}
}
Expand All @@ -151,7 +153,7 @@ async function fetchContributors(dates){
allContributorsSinceTwoMonthsAgo = allContributorsSince;
}
}
return [allContributorsSinceOneMonthAgo, allContributorsSinceTwoMonthsAgo, inactiveWithOpenIssue];
return [allContributorsSinceOneMonthAgo, allContributorsSinceTwoMonthsAgo, inactiveWithOpenSkills, inactiveWithOpenIssue];
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const fs = require('fs');
const getTeamMembers = require('../../utils/get-team-members');
const addTeamMember = require('../../utils/add-team-member');
const getOpenAssignedIssues = require('../../utils/get-open-assigned-issues');

// Global variables
var github;
Expand All @@ -10,6 +11,7 @@ var context;
const baseTeam = 'website';
const writeTeam = 'website-write';
const mergeTeam = 'website-merge';
const PMTeam = 'website-pm';



Expand All @@ -21,10 +23,11 @@ const mergeTeam = 'website-merge';
* @param {Object} c - context object from actions/github-script
* @param {Object} recentContributors - recentContributors since the dates[0] = recent cutoff
* @param {Object} previousContributors - previousContributors since the dates[1] = previous cutoff
* @param {Object} inactiveWithOpenSkills- Inactive contributors that have open Skills issues
* @param {Object} inactiveWithOpenIssue - Inactive contributors that have open issues
* @param {Object} dates - [recent, previous] dates of oneMonthAgo, twoMonthsAgo
*/
async function main({ g, c }, { recentContributors, previousContributors, inactiveWithOpenIssue, dates }) {
async function main({ g, c }, { recentContributors, previousContributors, inactiveWithOpenSkills, inactiveWithOpenIssue, dates }) {
github = g;
context = c;

Expand All @@ -33,69 +36,71 @@ async function main({ g, c }, { recentContributors, previousContributors, inacti
console.log('Current members of ' + writeTeam + ':');
console.log(currentTeamMembers);

const [removedContributors, cannotRemoveYet] = await removeInactiveMembers(previousContributors, inactiveWithOpenIssue, currentTeamMembers);
const removedContributors = await removeInactiveMembers(previousContributors, inactiveWithOpenSkills, inactiveWithOpenIssue, currentTeamMembers);
console.log(`-`.repeat(60));
console.log('Removed members from ' + writeTeam + ' inactive since ' + dates[1].slice(0, 10) + ':');
console.log(removedContributors);

console.log(`-`.repeat(60));
console.log('Members inactive since ' + dates[1].slice(0, 10) + ' with open issues preventing removal:');
console.log(cannotRemoveYet);

// Repeat getTeamMembers() after removedContributors to compare with recentContributors
const updatedTeamMembers = await getTeamMembers(github, context, writeTeam);
const notifiedContributors = await notifyInactiveMembers(updatedTeamMembers, recentContributors);
console.log(`-`.repeat(60));
console.log('Notified members from ' + writeTeam + ' inactive since ' + dates[0].slice(0, 10) + ':');
console.log(notifiedContributors);

writeData(removedContributors, notifiedContributors, cannotRemoveYet);
// Final pass to get all open, assigned issues to check whether assignee either isn't a team member or is inactive
const currentPMTeam = await getTeamMembers(github, context, PMTeam);
const writeAndPMTeam = { ...updatedTeamMembers, ...currentPMTeam };
const [nonTeamOpenIssue, inactiveOpenIssue] = await getOpenAssignedIssues(github, context, writeAndPMTeam, inactiveWithOpenIssue);
console.log(`-`.repeat(60));
console.log('Members inactive since ' + dates[1].slice(0, 10) + ' with open issues preventing removal:');
console.log(inactiveOpenIssue);

writeData(removedContributors, notifiedContributors, nonTeamOpenIssue, inactiveOpenIssue);
};



/**
* Remove contributors that were last active **before** the previous (twoMonthsAgo) date
* @param {Object} previousContributors - List of contributors active since previous date
* @param {Object} inactiveWithOpenSkills - Inactive members with open Skills Issue
* @param {Object} inactiveWithOpenIssue - Inactive members with open issues
* @returns {Array} removedMembers - List of members that were removed
* @returns {Object} cannotRemoveYet - List of members that cannot be removed due to open issues
* @param {Object} currentTeamMembers - Current team members
* @returns {Object} removedMembers - List of members that were removed
*/
async function removeInactiveMembers(previousContributors, inactiveWithOpenIssue, currentTeamMembers){
async function removeInactiveMembers(previousContributors, inactiveWithOpenSkills, inactiveWithOpenIssue, currentTeamMembers) {
const removedMembers = [];
const cannotRemoveYet = {};
const previouslyNotified = await readPreviousNotifyList();

// Loop over team members and remove them from the team if they are not in previousContributors list
for(const username in currentTeamMembers){
if (!previousContributors[username]){
// Loop over team members and remove them from the team if they
// are not in either previousContributors or inactiveWithOpenIssue
for (const username in currentTeamMembers) {
if ((!previousContributors[username]) || !(username in inactiveWithOpenIssue)) {
// Prior to deletion, confirm that member is on the baseTeam
await addTeamMember(github, context, baseTeam, username);
// But if member has an open issue or was not on the previouslyNotified list, do not remove yet
if(username in inactiveWithOpenIssue && inactiveWithOpenIssue[username][1] === false){
cannotRemoveYet[username] = inactiveWithOpenIssue[username][0];
} else if((previouslyNotified.length > 0) && !(previouslyNotified.includes(username))){
console.log('Member was not on last month\'s \'Inactive Members\' list, do not remove: ' + username);
// If member was not on the previouslyNotified list, do not remove yet
if ((previouslyNotified.length > 0) && !(previouslyNotified.includes(username))) {
console.log(`Member was not on last month's 'Inactive Members' list, do not remove: ${username}`);
} else {
// Remove member from all teams (except baseTeam)
const teams = [writeTeam, mergeTeam];
for(const team of teams){
for (const team of [writeTeam, mergeTeam]) {
// https://docs.github.com/en/rest/teams/members?apiVersion=2022-11-28#remove-team-membership-for-a-user
await github.request('DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}', {
org: context.repo.owner,
team_slug: team,
username: username,
username,
});
}
removedMembers.push(username);
// After removal, close member's "Skills Issue", if open
if(username in inactiveWithOpenIssue && inactiveWithOpenIssue[username][1] === true){
closePrework(username, inactiveWithOpenIssue[username][0]);
if (username in inactiveWithOpenSkills) {
closePrework(username, inactiveWithOpenSkills[username]);
}
}
}
}
return [removedMembers, cannotRemoveYet];
return removedMembers;
}


Expand Down Expand Up @@ -200,14 +205,15 @@ async function checkMemberIsNotNew(member){

/**
* Function to save inactive members list to local repo for use in next job
* @param {Array} removedContributors - List of contributors that were removed
* @param {Array} notifiedContributors - List of contributors to be notified
* @param {Array} cannotRemoveYet - List of contributors that can't be removed yet
* @param {Array} removedContributors - List of contributors that were removed
* @param {Array} notifiedContributors - List of contributors to be notified
* @param {Array} nonTeamOpenIssue - List of non-team members with open issues
* @param {Array} inactiveOpenIssue - List of inactive members that can't be removed yet
*/
function writeData(removedContributors, notifiedContributors, cannotRemoveYet){
function writeData(removedContributors, notifiedContributors, nonTeamOpenIssue, inactiveOpenIssue) {

const filepath = 'github-actions/utils/_data/inactive-members.json';
const inactiveMemberLists = { removedContributors, notifiedContributors, cannotRemoveYet };
const inactiveMemberLists = { removedContributors, notifiedContributors, nonTeamOpenIssue, inactiveOpenIssue };

fs.writeFile(filepath, JSON.stringify(inactiveMemberLists, null, 2), (err) => {
if (err) throw err;
Expand Down
52 changes: 52 additions & 0 deletions github-actions/utils/get-open-assigned-issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Function to get all repo issues that either are not assigned to a currentTeam member, or are assigned to
* inactive members so that leadership can be made aware that the issue does not have an active team member assigned.
* @param {Object} currentTeam - currentTeam members (optional)
* @param {Object} inactiveMemberOpenIssue - inactive team members assigned to an open issue (optional)
* @return {Object} nonTeamMemberOpenIssue - non-team members assigned to open issues
* @return {Object} inactiveMemberOpenIssue - inactive team members, all assignments to open issues
*/
async function getOpenAssignedIssues(github, context, currentTeam = {}, inactiveMemberOpenIssue = {}) {
let nonTeamMemberOpenIssue = {};
let pageNum = 1;
let result = [];

// Since Github only allows to fetch max 100 items per request, we need to 'flip' pages
while (true) {
// Fetch 100 items per each page (`pageNum`)
// https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues
const openIssues = await github.request('GET /repos/{owner}/{repo}/issues', {
owner: context.repo.owner,
repo: context.repo.repo,
assignee: '*',
per_page: 100,
page: pageNum
});

// If the API call returns an empty array, break out of loop- there is no additional data.
// Else if data is returned, push it to `result` and increase the page number (`pageNum`)
if (!openIssues.data.length) {
break;
} else {
result = result.concat(openIssues.data);
pageNum++;
}
}

// Loop through each result individually
for (const contributorInfo of result) {
let assignee = contributorInfo.assignee.login;
let issueNum = contributorInfo.number;
// Check if assignee is not a currentTeam member, then find their other open issues
// Else if assignee is on the inactiveMember list, find all of their open issues
if (!(assignee in currentTeam)) {
(nonTeamMemberOpenIssue[assignee] ??= []).push(issueNum);
} else if (assignee in inactiveMemberOpenIssue && !inactiveMemberOpenIssue[assignee].includes(issueNum)) {
inactiveMemberOpenIssue[assignee].push(issueNum);
}
}

return [nonTeamMemberOpenIssue, inactiveMemberOpenIssue];
}

module.exports = getOpenAssignedIssues;