Skip to content

Commit

Permalink
Update vote_tracker.js
Browse files Browse the repository at this point in the history
  • Loading branch information
AayushSaini101 committed Jul 17, 2024
1 parent 2ce1a67 commit 3173ed1
Showing 1 changed file with 157 additions and 129 deletions.
286 changes: 157 additions & 129 deletions .github/scripts/vote_tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,129 +14,113 @@ module.exports = async ({ context }) => {
// Path to the vote tracking file
const voteTrackingFile = path.join('voteTrackingFile.json');

let votingRows;
try {
// Parse the vote-closed comment to get voting rows
votingRows = await parseVoteClosedComment();
} catch (error) {
throw new Error('Error parsing vote-closed comment: ' + error.message);
}
// Parse the vote-closed comment created by git-vote[bot]
const votingRows = await parseVoteClosedComment();

// Example table vote comment that is parsed here https://github.com/asyncapi/community/issues/1227#issuecomment-2167463252
const latestVotes = votingRows.map(row => {
const [, user, vote, timestamp] = row.split('|').map(col => col.trim());
return { user: user.replace('@', ''), vote, timestamp, isVotedInLast3Months: true };
});

let maintainerInfo;
try {
// Read and parse the MAINTAINERS.yaml file
maintainerInfo = await readFile('MAINTAINERS.yaml', 'utf8');
} catch (error) {
throw new Error('Error reading MAINTAINERS.yaml file: ' + error.message);
}
//skipping first element as parsing is based on split, so table where column starts with | will have first element of created array empty
const [, user, vote, timestamp] = row.split('|').map(col => col.trim());
return { user: user.replace('@', ''), vote, timestamp, isVotedInLast3Months: true };
});

let maintainerInformation;
try {
const maintainerInfo = await readFile('MAINTAINERS.yaml', 'utf8');
maintainerInformation = yaml.load(maintainerInfo);
} catch (error) {
throw new Error('Error parsing MAINTAINERS.yaml file: ' + error.message);
} catch (readError) {
console.error('Error reading MAINTAINERS.yaml:', readError);
throw readError;
}

try {
// Update the TSC Members if new one added in the community
await updateVoteTrackingFile();
} catch (error) {
throw new Error('Error updating vote tracking file: ' + error.message);
}

let voteDetails;
try {
// Read and parse the vote tracking file
voteDetails = JSON.parse(await readFile(voteTrackingFile, 'utf8'));
} catch (error) {
throw new Error('Error reading voteTrackingFile.json: ' + error.message);
}
// Update the TSC Members
const voteDetails = await updateVoteTrackingFile();

const updatedVoteDetails = [];

// Process each vote detail to update voting information
voteDetails.forEach(voteInfo => {
// Checking only valid vote done by TSC Member
const isTscMember = maintainerInformation.some(item => item.github === voteInfo.name);

if (isTscMember) {
const currentTime = new Date().toISOString().split('T')[0];
const userInfo = latestVotes.find(vote => vote.user === voteInfo.name);
const voteChoice = userInfo ? userInfo.vote : "Not participated";

if (userInfo) {
voteInfo.isVotedInLast3Months = true;
voteInfo.lastParticipatedVoteTime = currentTime;
voteInfo[voteChoice === "In favor" ? 'agreeCount' : voteChoice === "Against" ? 'disagreeCount' : 'abstainCount']++;
} else {
voteInfo.notParticipatingCount++;
voteInfo.lastVoteClosedTime = currentTime;
if (!isVotingWithinLastThreeMonths(voteInfo)) {
voteInfo.isVotedInLast3Months = false;
}
const userVote = latestVotes.find(vote => vote.user === voteInfo.name);
let currentTime;
if (userVote && userVote.timestamp) {
currentTime = userVote.timestamp.toString().split(" ")[0];
}
const userInfo = latestVotes.find(vote => vote.user === voteInfo.name);
const voteChoice = userInfo ? userInfo.vote : "Not participated";
voteInfo.lastVoteClosedTime = new Date().toISOString().split('T')[0];

if (userInfo) {
voteInfo.isVotedInLast3Months = true;
voteInfo.lastParticipatedVoteTime = currentTime;
voteInfo[voteChoice === "In favor" ? 'agreeCount' : voteChoice === "Against" ? 'disagreeCount' : 'abstainCount']++;
} else {
voteInfo.notParticipatingCount++;
if (isVotingWithinLastThreeMonths(voteInfo)) {
voteInfo.isVotedInLast3Months = false;
}

// Update vote information with the issue title and number
// The order is after name the Issue number or PR number should be present
let updatedVoteInfo = {};
Object.keys(voteInfo).forEach(key => {
if (key === 'name') {
updatedVoteInfo['name'] = voteInfo.name;
updatedVoteInfo[eventTitle + "$$" + eventNumber] = voteChoice;
} else {
updatedVoteInfo[key] = voteInfo[key];
}
});
updatedVoteDetails.push(updatedVoteInfo);
}

// Update vote information with the issue title and number
let updatedVoteInfo = {};
Object.keys(voteInfo).forEach(key => {
if (key === 'name') {
updatedVoteInfo['name'] = voteInfo.name;
updatedVoteInfo[eventTitle + "$$" + eventNumber] = voteChoice;
} else {
updatedVoteInfo[key] = voteInfo[key];
}
});
updatedVoteDetails.push(updatedVoteInfo);
});

try {
// Write the updated vote details back to the file
await writeFile(voteTrackingFile, JSON.stringify(updatedVoteDetails, null, 2));
} catch (writeError) {
throw new Error('Error writing to voteTrackingFile.json: ' + writeError.message);
console.error('Error writing to voteTrackingFile.json:', writeError);
throw writeError;
}

let markdownTable;
const markdownTable = await jsonToMarkdownTable(updatedVoteDetails);
try {
// Generate the markdown table and write it to a file
markdownTable = await jsonToMarkdownTable(updatedVoteDetails);
} catch (error) {
throw new Error('Error generating markdown table: ' + error.message);
}

try {
await writeFile('voteTrackingDetails.md', markdownTable);
console.log('Markdown table has been written to voteTrackingDetails.md');
await writeFile('TSC_VOTING_OVERVIEW.md', markdownTable);
console.log('Markdown table has been written to TSC_VOTING_OVERVIEW.md');
} catch (writeError) {
throw new Error('Error writing to voteTrackingDetails.md: ' + writeError.message);
console.error('Error writing to TSC_VOTING_OVERVIEW.md:', writeError);
throw writeError;
}

// Functions are defined below
// Convert JSON data to a markdown table
async function jsonToMarkdownTable(data) {
const keys = Object.keys(data[0]);
if (!data || data.length === 0) {
console.error("Data is empty or undefined");
return '';
}
const keys = Object.keys(data[0]).filter(key => key !== 'firstVoteClosedTime');

const titles = {
name: "GitHub user name",
lastParticipatedVoteTime: "Last time the TSC member participated in a vote",
hasVotedInLast3Months: "Flag indicating if TSC member voted in last 3 months. This information is calculated after each voting, and not basing on a schedule as there might be moments when there is no voting in place for 3 months and therefore no TSC member votes.",
lastVoteClosedTime: "Date when last vote was closed. It indicated when the last voting took place and marks the date when this tracking document was updated.",
agreeCount: "Number of times TSC member agreed in a vote.",
disagreeCount: "Number of times TSC member did not agree in a vote.",
abstainCount: "Number of times TSC member abstained from voting.",
notParticipatingCount: "Number of times TSC member did not participate in voting."
};

// Fill missing properties with default values and log the processing
data = data.map((obj, index) => {
const newObj = {};
keys.forEach(key => {
newObj[key] = obj[key] !== undefined ? obj[key] : 'N/A';
});
return newObj;
});

let markdownTable = '| ' + keys.map(key => {
if (key.includes('$$')) {
const [title, number] = key.split('$$');
return `[${title}](https://github.com/${orgName}/${repoName}/issues/${number})`;
}
const titles = {
name: "GitHub user name",
lastParticipatedVoteTime: "Last time the TSC member participated in a vote",
hasVotedInLast3Months: "Flag indicating if TSC member voted in last 3 months. This information is calculated after each voting, and not basing on a schedule as there might be moments when there is no voting in place for 3 months and therefore no TSC member votes.",
lastVoteClosedTime: "Date when last vote was closed. It indicated when the last voting took place and marks the date when this tracking document was updated.",
agreeCount: "Number of times TSC member agreed in a vote.",
disagreeCount: "Number of times TSC member did not agree in a vote.",
abstainCount: "Number of times TSC member abstained from voting.",
notParticipatingCount: "Number of times TSC member did not participate in voting."
};
return `<span style="position: relative; cursor: pointer;" title="${titles[key] || key}">${key}</span>`;
}).join(' | ') + ' |\n';

Expand All @@ -159,6 +143,7 @@ module.exports = async ({ context }) => {
}

// Parse the vote-closed comment created by git-vote[bot]
// No need to look for "Vote closed" as this is already validated by the workflow that runs this code
async function parseVoteClosedComment() {
const bindingVotesSectionMatch = message.match(/Binding votes \(\d+\)[\s\S]*?(?=(<details>|$))/);
const bindingVotesSection = bindingVotesSectionMatch ? bindingVotesSectionMatch[0] : '';
Expand All @@ -168,49 +153,92 @@ module.exports = async ({ context }) => {
// Check if voting duration is within the last three months
function isVotingWithinLastThreeMonths(voteInfo) {
const currentDate = new Date();
const threeMonthsAgoDate = new Date(currentDate);
threeMonthsAgoDate.setMonth(currentDate.getMonth() - 3);
return new Date(voteInfo.lastVoteClosedTime) >= threeMonthsAgoDate &&
new Date(voteInfo.lastParticipatedVoteTime) <= threeMonthsAgoDate;
let previousDate;
if (voteInfo.isVotedInLast3Months === "Member has not voted in all previous voting process.") {
previousDate = new Date(voteInfo.firstVoteClosedTime);
} else {
previousDate = new Date(voteDetails.lastVoteClosedTime);
}
const yearDiff = currentDate.getFullYear() - previousDate.getFullYear();
const monthDiff = currentDate.getMonth() - previousDate.getMonth();
const totalMonthsDiff = yearDiff * 12 + monthDiff;

return totalMonthsDiff > 3;
}

// Function to update the voteTrackingFile with updated TSC Members
// Function to update the voteTrackingFile with updated TSC Members
async function updateVoteTrackingFile() {
const tscMembers = maintainerInformation.filter(entry => entry.isTscMember);
let voteDetails = [];
try {
voteDetails = JSON.parse(await readFile(voteTrackingFile, 'utf8'));
} catch (readError) {
console.error('Error reading voteTrackingFile.json:', readError);
}

const newTSCMembers = [];

tscMembers.forEach(member => {
const existingMember = voteDetails.find(voteInfo => voteInfo.name === member.github);
if (!existingMember) {
newTSCMembers.push({
name: member.github,
lastParticipatedVoteTime: '',
isVotedInLast3Months: 'false',
lastVoteClosedTime: new Date().toISOString().split('T')[0],
agreeCount: 0,
disagreeCount: 0,
abstainCount: 0,
notParticipatingCount: 0
});
}
});
if (newTSCMembers.length > 0) {
const tscMembers = maintainerInformation.filter(entry => entry.isTscMember);
let voteDetails = [];
try {
const combinedData = [...voteDetails, ...newTSCMembers];
await writeFile(voteTrackingFile, JSON.stringify(combinedData, null, 2));
} catch (writeError) {
console.error('Error writing to voteTrackingFile.json:', writeError);
voteDetails = JSON.parse(await readFile(voteTrackingFile, 'utf8'));
} catch (readError) {
console.error('Error reading voteTrackingFile.json:', readError);
throw readError;
}
}
const updatedTSCMembers = [];
const requiredKeys = ['name', 'lastParticipatedVoteTime', 'isVotedInLast3Months', 'lastVoteClosedTime', 'agreeCount', 'disagreeCount', 'abstainCount', 'notParticipatingCount'];
// Function to check if an object has all required keys
const isValidExampleMember = (member) => {
return requiredKeys.every(key => member.hasOwnProperty(key));
};
// Find the first valid example member
const validExampleMember = voteDetails.find(isValidExampleMember);

if (validExampleMember) {
tscMembers.forEach(member => {
const existingMember = voteDetails.find(voteInfo => voteInfo.name === member.github);
if (!existingMember) {
// Create a new member by copying the structure of the valid example member
const newMember = {};

// Copy the keys from the valid example member to the new member with default values
Object.keys(validExampleMember).forEach(key => {
switch (key) {
case 'name':
newMember[key] = member.github;
break;
case 'lastParticipatedVoteTime':
newMember[key] = 'Member has not participated in all previous voting process.';
break;
case 'isVotedInLast3Months':
newMember[key] = 'Member has not participated in all previous voting process.';
break;
case 'lastVoteClosedTime':
newMember[key] = new Date().toISOString().split('T')[0];
break;
case 'firstVoteClosedTime':
newMember[key] = validExampleMember['firstVoteClosedTime']; // This is used to determine when the first vote closed so that we can determine the duration between two votes easily
break;
case 'agreeCount':
case 'disagreeCount':
case 'abstainCount':
case 'notParticipatingCount':
newMember[key] = 0;
break;
default:
newMember[key] = "Not participated";
}
});

updatedTSCMembers.push(newMember);
}
});
} else {
console.log('No valid example member found in voteDetails.');
}

if (updatedTSCMembers.length > 0) {
try {
const combinedData = [...voteDetails, ...updatedTSCMembers];
await writeFile(voteTrackingFile, JSON.stringify(combinedData, null, 2));
return combinedData; // Return the updated data
} catch (writeError) {
console.error('Error wile writing file:' ,writeError)
}
}
}
} catch (error) {
} catch (error) {
console.error('Error while running the vote_tracker workflow:', error);
}
};
}

0 comments on commit 3173ed1

Please sign in to comment.