|
| 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 | +}; |
0 commit comments