diff --git a/.github/scripts/vote_tracker.js b/.github/scripts/vote_tracker.js index 0112cde33..6b4a93d85 100644 --- a/.github/scripts/vote_tracker.js +++ b/.github/scripts/vote_tracker.js @@ -1,128 +1,187 @@ const yaml = require('js-yaml'); -const fs = require('fs'); +const { readFile, writeFile } = require('fs').promises; const path = require('path'); -module.exports = async ({ github, context, core }) => { - - const message = context.payload.comment.body; - const eventNumber = context.issue.number; - const eventTitle = context.payload.issue.title - const orgName = context.issue.owner - const repoName = context.issue.repo - - const filePath = path.join('VoteTracking.json'); - - const bindingVotesSectionMatch = message.match(/Binding votes \(\d+\)[\s\S]*?(?=(
|$))/); - const bindingVotesSection = bindingVotesSectionMatch ? bindingVotesSectionMatch[0] : ''; - const rows = bindingVotesSection.match(/\| @\w+.*?\|.*?\|.*?\|/g) || []; - const latestVotes = rows.map(row => { - const [, user, vote, timestamp] = row.split('|').map(col => col.trim()); - return { user: user.replace('@', ''), vote, timestamp, isVotedInLast3Months: true }; - }); - - const yamlData = fs.readFileSync('MAINTAINERS.yaml', 'utf8'); - const parsedData = yaml.load(yamlData); - // Initialize vote tracking file if it doesn't exist - if (!fs.existsSync(filePath)) { - const tscMembers = parsedData.filter(entry => entry.isTscMember).map(entry => ({ - name: entry.github, - lastParticipatedVoteTime: '', - isVotedInLast3Months: 'false', - lastVoteClosedTime: new Date().toISOString().split('T')[0], - agreeCount: 0, - disagreeCount: 0, - abstainCount: 0, - notParticipatingCount: 0 - })); - fs.writeFileSync(filePath, JSON.stringify(tscMembers, null, 2)); - } +module.exports = async ({ context }) => { + try { + // Extract necessary details from the context + const message = context.payload.comment.body; + const eventNumber = context.issue.number; + const eventTitle = context.payload.issue.title; + const orgName = context.issue.owner; + const repoName = context.issue.repo; + + // Path to the vote tracking file + const voteTrackingFile = path.join('voteTrackingFile.json'); + + // Parse the vote-closed comment to get voting rows + const votingRows = await parseVoteClosedComment(); + + // Extract latest votes information from the parsed voting rows + const latestVotes = votingRows.map(row => { + const [, user, vote, timestamp] = row.split('|').map(col => col.trim()); + return { user: user.replace('@', ''), vote, timestamp, isVotedInLast3Months: true }; + }); + + // Read and parse the MAINTAINERS.yaml file + const maintainerInfo = await readFile('MAINTAINERS.yaml', 'utf8'); + const maintainerInformation = yaml.load(maintainerInfo); + + // Update the TSC Members if new one added in the community + await updateVoteTrackingFile(); + + // Read and parse the vote tracking file + const voteDetails = JSON.parse(await readFile(voteTrackingFile, 'utf8')); + + const updatedVoteDetails = []; - const voteDetails = JSON.parse(fs.readFileSync(filePath, 'utf8')); - const latestVotesInfo = [] - voteDetails.map(voteInfo => { - const checkPersonisTSC = parsedData.some(item => item.github === voteInfo.name); - if (checkPersonisTSC) { - const currentTime = new Date().toISOString().split('T')[0]; - const userInfo = latestVotes.find(vote => vote.user === voteInfo.name); - const choice = userInfo ? userInfo.vote : "Not participated"; - - if (userInfo) { - voteInfo.isVotedInLast3Months = true; - voteInfo.lastParticipatedVoteTime = currentTime; - voteInfo[choice === "In favor" ? 'agreeCount' : choice === "Against" ? 'disagreeCount' : 'abstainCount']++; - } else { - voteInfo.notParticipatingCount++; - voteInfo.lastVoteClosedTime = currentTime; - if (!checkVotingDurationMoreThanThreeMonths(voteInfo)) { - voteInfo.isVotedInLast3Months = false; + // 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; + } } + + // 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); } - - let updatedVoteInfo = {}; - Object.keys(voteInfo).forEach(key => { - if (key == 'name') { - updatedVoteInfo['name'] = voteInfo.name - updatedVoteInfo[eventTitle + "$$" + eventNumber] = choice + }); + + // Write the updated vote details back to the file + try { + await writeFile(voteTrackingFile, JSON.stringify(updatedVoteDetails, null, 2)); + } catch (writeError) { + console.error('Error writing to voteTrackingFile.json:', writeError); + } + + // Generate the markdown table and write it to a file + const markdownTable = await jsonToMarkdownTable(updatedVoteDetails); + try { + await writeFile('voteTrackingDetails.md', markdownTable); + console.log('Markdown table has been written to voteTrackingDetails.md'); + } catch (writeError) { + console.error('Error writing to voteTrackingDetails.md:', writeError); + } + + // Functions are defined below + // Convert JSON data to a markdown table + async function jsonToMarkdownTable(data) { + const keys = Object.keys(data[0]); + let markdownTable = '| ' + keys.map(key => { + if (key.includes('$$')) { + const [title, number] = key.split('$$'); + return `[${title}](https://github.com/${orgName}/${repoName}/issues/${number})`; } - else { - updatedVoteInfo[key] = voteInfo[key]; + 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 `${key}`; + }).join(' | ') + ' |\n'; + + markdownTable += '| ' + keys.map(() => '---').join(' | ') + ' |\n'; + markdownTable += data.map(obj => '| ' + keys.map(key => { + if (key === 'name') return `[${obj[key]}](https://github.com/${obj[key]})`; + if (key.includes('$$')) { + const icons = { + "In favor": "👍", + "Against": "👎", + "Abstain": "👀", + "Not participated": "🔕" + }; + return `${icons[obj[key]] || obj[key]}`; } - }) - latestVotesInfo.push(updatedVoteInfo) + return obj[key]; + }).join(' | ') + ' |').join('\n'); + + return markdownTable; } - }); - - fs.writeFileSync(filePath, JSON.stringify(latestVotesInfo, null, 2)); - - // Check voting duration - function checkVotingDurationMoreThanThreeMonths(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; - } - // Convert JSON data to markdown table - function jsonToMarkdownTable(data) { - const keys = Object.keys(data[0]); - 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 `${key}`; - }).join(' | ') + ' |\n'; - - markdownTable += '| ' + keys.map(() => '---').join(' | ') + ' |\n'; - markdownTable += data.map(obj => '| ' + keys.map(key => { - if (key === 'name') return `[${obj[key]}](https://github.com/${obj[key]})`; - if (key.includes('$$')) { - const icons = { - "In favor": "👍", - "Against": "👎", - "Abstain": "👀", - "Not participated": "🔕" - }; - return `${icons[obj[key]] || obj[key]}`; + // Parse the vote-closed comment created by git-vote[bot] + async function parseVoteClosedComment() { + const bindingVotesSectionMatch = message.match(/Binding votes \(\d+\)[\s\S]*?(?=(
|$))/); + const bindingVotesSection = bindingVotesSectionMatch ? bindingVotesSectionMatch[0] : ''; + return bindingVotesSection.match(/\| @\w+.*?\|.*?\|.*?\|/g) || []; + } + + // 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; + } + + // 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); } - return obj[key]; - }).join(' | ') + ' |').join('\n'); - return markdownTable; + 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 (updatedTSCMembers.length > 0) { + try { + const combinedData = [...voteDetails, ...newTSCMembers]; + await writeFile(voteTrackingFile, JSON.stringify(combinedData, null, 2)); + } catch (writeError) { + console.error('Error writing to voteTrackingFile.json:', writeError); + } + } + } + } catch (error) { + console.error('Error in while running the vote_tracker workflow:', error); } - const markdownTable = jsonToMarkdownTable(latestVotesInfo); - fs.writeFileSync('voteTrackingDetails.md', markdownTable); - console.log('Markdown table has been written to voteTrackingDetails.md'); -} \ No newline at end of file +}; diff --git a/voteTrackingFile.json b/voteTrackingFile.json new file mode 100644 index 000000000..cc3132709 --- /dev/null +++ b/voteTrackingFile.json @@ -0,0 +1,12 @@ +[ + { + "name": "Mayaleeeee", + "lastParticipatedVoteTime": "", + "isVotedInLast3Months": "false", + "lastVoteClosedTime": "2024-07-07", + "agreeCount": 0, + "disagreeCount": 0, + "abstainCount": 0, + "notParticipatingCount": 0 + } +] \ No newline at end of file