Skip to content

Commit 0800409

Browse files
authored
Merge branch 'master' into docs/weights-and-frontmatter
2 parents ec4993b + 41efd0c commit 0800409

File tree

11 files changed

+663
-97
lines changed

11 files changed

+663
-97
lines changed

.github/scripts/vote_tracker/index.js

Lines changed: 2 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const yaml = require("js-yaml");
22
const { readFile, writeFile } = require("fs").promises;
33
const path = require("path");
44
const { isVotingWithinLastThreeMonths } = require("./utils");
5+
const jsonToMarkdownTable = require("./markdownTable.js");
56
module.exports = async ({ github, context, botCommentURL }) => {
67
try {
78
let message, eventNumber, eventTitle, orgName, repoName;
@@ -131,7 +132,7 @@ module.exports = async ({ github, context, botCommentURL }) => {
131132
throw writeError;
132133
}
133134

134-
const markdownTable = await jsonToMarkdownTable(updatedVoteDetails);
135+
const markdownTable = await jsonToMarkdownTable(updatedVoteDetails, orgName, repoName);
135136
try {
136137
await writeFile("TSC_VOTING_OVERVIEW.md", markdownTable);
137138
console.log("Markdown table has been written to TSC_VOTING_OVERVIEW.md");
@@ -140,84 +141,6 @@ module.exports = async ({ github, context, botCommentURL }) => {
140141
throw writeError;
141142
}
142143

143-
async function jsonToMarkdownTable(data) {
144-
if (!data || data.length === 0) {
145-
console.error("Data is empty or undefined");
146-
return "";
147-
}
148-
149-
const keys = Object.keys(data[0]).filter(
150-
(key) => key !== "firstVoteClosedTime"
151-
);
152-
153-
const titles = {
154-
name: "GitHub user name",
155-
lastParticipatedVoteTime:
156-
"Last time the TSC member participated in a vote",
157-
hasVotedInLast3Months:
158-
"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.",
159-
lastVoteClosedTime:
160-
"Date when last vote was closed. It indicated when the last voting took place and marks the date when this tracking document was updated.",
161-
agreeCount: "Number of times TSC member agreed in a vote.",
162-
disagreeCount: "Number of times TSC member did not agree in a vote.",
163-
abstainCount: "Number of times TSC member abstained from voting.",
164-
notParticipatingCount:
165-
"Number of times TSC member did not participate in voting.",
166-
};
167-
168-
// Fill missing properties with default values and log the processing
169-
data = data.map((obj, index) => {
170-
const newObj = {};
171-
keys.forEach((key) => {
172-
newObj[key] = obj[key] !== undefined ? obj[key] : "N/A";
173-
});
174-
return newObj;
175-
});
176-
177-
let markdownTable =
178-
"<!-- This file is generated by a script. Do not manually update it unless there is a visible mistake and point to the script that is responsible for updating the document. -->\n";
179-
markdownTable +=
180-
"| " +
181-
keys
182-
.map((key) => {
183-
if (key.includes("$$")) {
184-
const [title, number] = key.split("$$");
185-
return `[${title}](https://github.com/${orgName}/${repoName}/issues/${number})`;
186-
}
187-
return `<span style="position: relative; cursor: pointer;" title="${titles[key] || key
188-
}">${key}</span>`;
189-
})
190-
.join(" | ") +
191-
" |\n";
192-
193-
markdownTable += "| " + keys.map(() => "---").join(" | ") + " |\n";
194-
markdownTable += data
195-
.map(
196-
(obj) =>
197-
"| " +
198-
keys
199-
.map((key) => {
200-
if (key === "name")
201-
return `[${obj[key]}](https://github.com/${obj[key]})`;
202-
if (key.includes("$$")) {
203-
const icons = {
204-
"In favor": "👍",
205-
Against: "👎",
206-
Abstain: "👀",
207-
"Not participated": "🔕",
208-
};
209-
return `<span style="position: relative; cursor: pointer;" title="${obj[key]
210-
}">${icons[obj[key]] || obj[key]}</span>`;
211-
}
212-
return obj[key];
213-
})
214-
.join(" | ") +
215-
" |"
216-
)
217-
.join("\n");
218-
219-
return markdownTable;
220-
}
221144

222145
// Parse the vote-closed comment created by git-vote[bot]
223146
// No need to look for "Vote closed" as this is already validated by the workflow that runs this code
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/**
2+
* Replaces voting status values with corresponding emoji icons wrapped in a tooltip span.
3+
* This function is used to visually represent vote statuses (e.g., "In favor", "Against") in a Markdown table
4+
* with user-friendly emojis, enhancing readability. The tooltip preserves the original status text for clarity
5+
* when users hover over the emoji.
6+
*
7+
* @param {string} value - The vote status to render (e.g., "In favor", "Against", "Abstain", "Not participated").
8+
* @returns {string} - An HTML string containing the emoji icon wrapped in a `<span>` element with a tooltip
9+
* displaying the original status. If no matching emoji is found, the original value is returned
10+
* wrapped in a tooltip span.
11+
* @example
12+
* renderVoteIcon("In favor") // Returns '<span style="position: relative; cursor: pointer;" title="In favor">👍</span>'
13+
*/
14+
function renderVoteIcon(value) {
15+
const icons = {
16+
"In favor": "👍",
17+
Against: "👎",
18+
Abstain: "👀",
19+
"Not participated": "🔕",
20+
};
21+
return `<span style="position: relative; cursor: pointer;" title="${value}">${icons[value] || value}</span>`;
22+
}
23+
24+
/**
25+
* Generates a Markdown table header cell, optionally with a tooltip or a GitHub issue link.
26+
* This function formats column headers in a Markdown table, enhancing them with tooltips for better context
27+
* or converting headers with issue references (e.g., "Title$$123") into clickable GitHub issue links.
28+
* It supports user-friendly table rendering in documentation.
29+
*
30+
* @param {string} key - The column header key to display (e.g., "name", "Proposal$$123").
31+
* @param {Object} titles - A mapping of header keys to human-readable descriptions for tooltips.
32+
* @param {string} orgName - The GitHub organization name (e.g., "my-org") for constructing issue links.
33+
* @param {string} repoName - The GitHub repository name (e.g., "my-repo") for constructing issue links.
34+
* @returns {string} - A Markdown or HTML string representing the header cell. If the key includes "$$",
35+
* it returns a Markdown link to a GitHub issue. Otherwise, it returns a `<span>` with
36+
* a tooltip containing the key's description or the key itself.
37+
* @example
38+
* renderHeaderCell("Proposal$$123", { "Proposal": "Proposal Description" }, "org", "repo")
39+
* // Returns '[Proposal](https://github.com/org/repo/issues/123)'
40+
* renderHeaderCell("name", { "name": "GitHub user name" }, "org", "repo")
41+
* // Returns '<span style="position: relative; cursor: pointer;" title="GitHub user name">name</span>'
42+
*/
43+
function renderHeaderCell(key, titles, orgName, repoName) {
44+
if (key.includes("$$")) {
45+
const [title, issueNumber] = key.split("$$");
46+
return `[${title}](https://github.com/${orgName}/${repoName}/issues/${issueNumber})`;
47+
}
48+
49+
const tooltip = titles[key] || key;
50+
return `<span style="position: relative; cursor: pointer;" title="${tooltip}">${key}</span>`;
51+
}
52+
53+
/**
54+
* Generates the Markdown header row and separator row for a table.
55+
* This function creates the top two rows of a Markdown table: the header row with column names
56+
* (optionally formatted with tooltips or GitHub issue links) and the separator row with dashes
57+
* to define the table structure. It ensures proper Markdown table syntax for rendering in documentation.
58+
*
59+
* @param {string[]} keys - An array of column header keys to include in the table.
60+
* @param {Object} titles - A mapping of header keys to human-readable descriptions for tooltips.
61+
* @param {string} orgName - The GitHub organization name for constructing issue links in headers.
62+
* @param {string} repoName - The GitHub repository name for constructing issue links in headers.
63+
* @returns {string} - A string containing the Markdown header row and separator row, separated by a newline.
64+
* @example
65+
* generateMarkdownHeader(["name", "Proposal$$123"], { name: "GitHub user name" }, "org", "repo")
66+
* // Returns:
67+
* // "| <span style=\"position: relative; cursor: pointer;\" title=\"GitHub user name\">name</span> | [Proposal](https://github.com/org/repo/issues/123) |\n| --- | --- |"
68+
*/
69+
function generateMarkdownHeader(keys, titles, orgName, repoName) {
70+
const headerRow = "| " + keys.map(key => renderHeaderCell(key, titles, orgName, repoName)).join(" | ") + " |";
71+
const separatorRow = "| " + keys.map(() => "---").join(" | ") + " |";
72+
return `${headerRow}\n${separatorRow}`;
73+
}
74+
75+
/**
76+
* Generates all Markdown table rows for the given data.
77+
* This function converts an array of data objects into Markdown table rows, formatting specific columns
78+
* (e.g., "name" as a GitHub profile link, vote status keys as emoji icons) to create a user-friendly table body.
79+
* Each row is formatted according to Markdown table syntax.
80+
*
81+
* @param {Object[]} data - An array of objects, where each object represents a table row with key-value pairs.
82+
* @param {string[]} keys - An array of keys defining the columns to render for each row.
83+
* @returns {string} - A string containing all Markdown table rows, separated by newlines.
84+
* @example
85+
* generateMarkdownRows([{ name: "user1", "Proposal$$123": "In favor" }], ["name", "Proposal$$123"])
86+
* // Returns:
87+
* // "| [user1](https://github.com/user1) | <span style=\"position: relative; cursor: pointer;\" title=\"In favor\">👍</span> |"
88+
*/
89+
function generateMarkdownRows(data, keys) {
90+
if (!data || data.length === 0) {
91+
console.error("No data provided to generateMarkdownRows.");
92+
return "";
93+
} else {
94+
console.log(
95+
"Markdown table rows generation with the following data as input:",
96+
JSON.stringify(data, null, 2)
97+
);
98+
}
99+
return data.map(row => {
100+
const rowStr = keys.map(key => {
101+
if (key === "name") {
102+
return `[${row[key]}](https://github.com/${row[key]})`;
103+
}
104+
if (key.includes("$$")) {
105+
return renderVoteIcon(row[key]);
106+
}
107+
return row[key];
108+
}).join(" | ");
109+
return `| ${rowStr} |`;
110+
}).join("\n");
111+
}
112+
113+
/**
114+
* Normalizes each object in the dataset to ensure all keys are present.
115+
* This function ensures that every row in the dataset has all expected columns by filling missing
116+
* values with "N/A". This prevents errors during table generation and ensures consistent column alignment
117+
* in the resulting Markdown table.
118+
*
119+
* @param {Object[]} data - An array of raw data objects, where each object represents a table row.
120+
* @param {string[]} keys - An array of expected keys (columns) for the table.
121+
* @returns {Object[]} - An array of normalized data objects, where each object contains all specified keys,
122+
* with missing values set to "N/A".
123+
* @example
124+
* normalizeTableData([{ name: "user1" }], ["name", "vote"])
125+
* // Returns [{ name: "user1", vote: "N/A" }]
126+
*/
127+
function normalizeTableData(data, keys) {
128+
return data.map((item) => {
129+
const normalized = {};
130+
keys.forEach((key) => {
131+
normalized[key] = item[key] !== undefined ? item[key] : "N/A";
132+
});
133+
return normalized;
134+
});
135+
}
136+
137+
/**
138+
* Converts an array of voting records into a Markdown-formatted table.
139+
* This function orchestrates the creation of a complete Markdown table from voting data, typically used
140+
* to document Technical Steering Committee (TSC) voting records in a GitHub repository. It processes the data
141+
* to generate a table with formatted headers (with tooltips or GitHub issue links), vote statuses as emojis,
142+
* and user names as GitHub profile links. The table is prefixed with a comment indicating it is script-generated.
143+
*
144+
* @param {Object[]} data - An array of voting records, where each object represents a row with keys like
145+
* "name", "lastParticipatedVoteTime", and vote statuses for specific proposals.
146+
* @param {string} orgName - The GitHub organization name (e.g., "my-org") for constructing links.
147+
* @param {string} repoName - The GitHub repository name (e.g., "my-repo") for constructing links.
148+
* @returns {Promise<string>} - A Promise resolving to the complete Markdown table as a string, including
149+
* a header comment, table headers, and table rows. Returns an empty string if
150+
* the input data is empty or undefined.
151+
* @example
152+
* jsonToMarkdownTable(
153+
* [{ name: "user1", "Proposal$$123": "In favor" }],
154+
* "org",
155+
* "repo"
156+
* )
157+
* // Returns a Promise resolving to:
158+
* // <!-- This file is generated by a script. Do not manually update it unless there is a visible mistake and point to the script that is responsible for updating the document. -->
159+
* // | <span style="position: relative; cursor: pointer;" title="GitHub user name">name</span> | [Proposal](https://github.com/org/repo/issues/123) |
160+
* // | --- | --- |
161+
* // | [user1](https://github.com/user1) | <span style="position: relative; cursor: pointer;" title="In favor">👍</span> |
162+
*/
163+
async function jsonToMarkdownTable(data, orgName, repoName) {
164+
if (!data || data.length === 0) {
165+
console.error("Data is empty or undefined");
166+
return "";
167+
}
168+
else {
169+
console.log(
170+
"Working on markdown generation with the following data as input:",
171+
JSON.stringify(data, null, 2)
172+
)
173+
}
174+
175+
const titles = {
176+
name: "GitHub user name",
177+
lastParticipatedVoteTime: "Last time the TSC member participated in a vote",
178+
hasVotedInLast3Months: "Flag indicating if TSC member voted in last 3 months",
179+
lastVoteClosedTime: "Date when last vote was closed",
180+
agreeCount: "Number of times TSC member agreed in a vote",
181+
disagreeCount: "Number of times TSC member did not agree in a vote",
182+
abstainCount: "Number of times TSC member abstained from voting",
183+
notParticipatingCount: "Number of times TSC member did not participate in voting",
184+
};
185+
186+
// We extract all keys from the first row of data to determine the columns for the table.
187+
// However, we explicitly **exclude "firstVoteClosedTime"** because:
188+
// * It is likely a metadata field (e.g., the date when the first vote was closed) that is irrelevant for the table’s purpose of documenting TSC voting records.
189+
// * Including it would clutter the table with information that does not contribute to the reader’s understanding of voting outcomes or participation.
190+
// * Filtering it ensures that `normalizeTableData` only processes relevant keys, maintaining a consistent table structure and avoiding unnecessary columns.
191+
192+
const keys = Object.keys(data[0]).filter(k => k !== "firstVoteClosedTime");
193+
const normalizedData = normalizeTableData(data, keys);
194+
195+
let markdown = `<!-- This file is generated by a script. Do not manually update it unless there is a visible mistake and point to the script that is responsible for updating the document. -->\n`;
196+
197+
markdown += generateMarkdownHeader(keys, titles, orgName, repoName) + "\n";
198+
markdown += generateMarkdownRows(normalizedData, keys);
199+
200+
return markdown;
201+
}
202+
203+
module.exports = {
204+
jsonToMarkdownTable,
205+
renderVoteIcon,
206+
renderHeaderCell,
207+
generateMarkdownHeader,
208+
generateMarkdownRows,
209+
};

.github/workflows/ambassador_management.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ jobs:
135135
console.log(`New ambassadors: ${newAmbassadors}`);
136136
const welcomeMessage = newAmbassadors.map((ambassador) => `@${ambassador.trim().replace(/^@/, '')} We invited you to join the AsyncAPI organization, and you are added to the team that lists all Ambassadors.\n
137137
138-
Welcome aboard! We are excited to have you as part of the team.`).join("\n");
138+
Welcome aboard! We are excited to have you as part of the team.`).join("\n");
139139
140140
const { owner, repo } = context.repo;
141141
const { number: issue_number } = context.issue;
@@ -179,7 +179,7 @@ jobs:
179179
- name: Display goodbye message to removed ambassadors
180180
uses: actions/github-script@v7
181181
with:
182-
github-token: ${{ secrets.GH_TOKEN_ORG_ADMIN }}
182+
github-token: ${{ secrets.GH_TOKEN }}
183183
script: |
184184
const removedAmbassadors = "${{ needs.remove_ambassador.outputs.removedAmbassadors }}".split(",");
185185

0 commit comments

Comments
 (0)