|
20 | 20 | // Note: This script copies discussion content, comments, replies, and basic metadata. |
21 | 21 | // Reactions and other advanced interactions are not copied. |
22 | 22 | // Attachments (images and files) will not copy over - they need manual handling. |
23 | | -// |
24 | | -// TODO: Polls don't copy options |
| 23 | + |
| 24 | +// Configuration |
| 25 | +const INCLUDE_POLL_MERMAID_CHART = true; // Set to false to disable Mermaid pie chart for polls |
25 | 26 |
|
26 | 27 | const { Octokit } = require("octokit"); |
27 | 28 |
|
@@ -89,6 +90,55 @@ async function rateLimitSleep(seconds = 2) { |
89 | 90 | await sleep(seconds); |
90 | 91 | } |
91 | 92 |
|
| 93 | +function formatPollData(poll) { |
| 94 | + if (!poll || !poll.options || poll.options.nodes.length === 0) { |
| 95 | + return ''; |
| 96 | + } |
| 97 | + |
| 98 | + const options = poll.options.nodes; |
| 99 | + const totalVotes = poll.totalVoteCount || 0; |
| 100 | + |
| 101 | + let pollMarkdown = '\n\n---\n\n### 📊 Poll Results (from source discussion)\n\n'; |
| 102 | + pollMarkdown += `**${poll.question}**\n\n`; |
| 103 | + |
| 104 | + // Create table |
| 105 | + pollMarkdown += '| Option | Votes | Percentage |\n'; |
| 106 | + pollMarkdown += '|--------|-------|------------|\n'; |
| 107 | + |
| 108 | + options.forEach(option => { |
| 109 | + const votes = option.totalVoteCount || 0; |
| 110 | + const percentage = totalVotes > 0 ? ((votes / totalVotes) * 100).toFixed(1) : '0.0'; |
| 111 | + pollMarkdown += `| ${option.option} | ${votes} | ${percentage}% |\n`; |
| 112 | + }); |
| 113 | + |
| 114 | + pollMarkdown += `\n**Total votes:** ${totalVotes}\n`; |
| 115 | + |
| 116 | + // Add Mermaid pie chart if enabled |
| 117 | + if (INCLUDE_POLL_MERMAID_CHART && totalVotes > 0) { |
| 118 | + pollMarkdown += '\n<details>\n<summary><i>Visual representation</i></summary>\n\n'; |
| 119 | + pollMarkdown += '```mermaid\n'; |
| 120 | + pollMarkdown += '%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%\n'; |
| 121 | + pollMarkdown += 'pie showData\n'; |
| 122 | + pollMarkdown += ` title ${poll.question}\n`; |
| 123 | + |
| 124 | + options.forEach(option => { |
| 125 | + const votes = option.totalVoteCount || 0; |
| 126 | + if (votes > 0) { |
| 127 | + // Escape quotes in option text for Mermaid |
| 128 | + const escapedOption = option.option.replace(/"/g, '\\"'); |
| 129 | + pollMarkdown += ` "${escapedOption}" : ${votes}\n`; |
| 130 | + } |
| 131 | + }); |
| 132 | + |
| 133 | + pollMarkdown += '```\n\n'; |
| 134 | + pollMarkdown += '</details>\n'; |
| 135 | + } |
| 136 | + |
| 137 | + pollMarkdown += '\n_Note: This is a static snapshot of poll results from the source discussion. Voting is not available in copied discussions._\n'; |
| 138 | + |
| 139 | + return pollMarkdown; |
| 140 | +} |
| 141 | + |
92 | 142 | // GraphQL Queries and Mutations |
93 | 143 | const CHECK_DISCUSSIONS_ENABLED_QUERY = ` |
94 | 144 | query($owner: String!, $repo: String!) { |
@@ -169,6 +219,16 @@ const FETCH_DISCUSSIONS_QUERY = ` |
169 | 219 | upvoteCount |
170 | 220 | url |
171 | 221 | number |
| 222 | + poll { |
| 223 | + question |
| 224 | + totalVoteCount |
| 225 | + options(first: 100) { |
| 226 | + nodes { |
| 227 | + option |
| 228 | + totalVoteCount |
| 229 | + } |
| 230 | + } |
| 231 | + } |
172 | 232 | } |
173 | 233 | } |
174 | 234 | } |
@@ -492,8 +552,17 @@ async function addLabelsToDiscussion(octokit, discussionId, labelIds) { |
492 | 552 | } |
493 | 553 | } |
494 | 554 |
|
495 | | -async function createDiscussion(octokit, repositoryId, categoryId, title, body, sourceUrl, sourceAuthor, sourceCreated) { |
496 | | - const enhancedBody = `${body}\n\n---\n<details>\n<summary><i>Original discussion metadata</i></summary>\n\n_Original discussion by @${sourceAuthor} on ${sourceCreated}_\n_Source: ${sourceUrl}_\n</details>`; |
| 555 | +async function createDiscussion(octokit, repositoryId, categoryId, title, body, sourceUrl, sourceAuthor, sourceCreated, poll = null) { |
| 556 | + let enhancedBody = body; |
| 557 | + |
| 558 | + // Add poll data if present |
| 559 | + if (poll) { |
| 560 | + const pollMarkdown = formatPollData(poll); |
| 561 | + enhancedBody += pollMarkdown; |
| 562 | + } |
| 563 | + |
| 564 | + // Add metadata |
| 565 | + enhancedBody += `\n\n---\n<details>\n<summary><i>Original discussion metadata</i></summary>\n\n_Original discussion by @${sourceAuthor} on ${sourceCreated}_\n_Source: ${sourceUrl}_\n</details>`; |
497 | 566 |
|
498 | 567 | log(`Creating discussion: '${title}'`); |
499 | 568 |
|
@@ -730,12 +799,18 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo, |
730 | 799 | discussion.body || "", |
731 | 800 | discussion.url, |
732 | 801 | discussion.author?.login || "unknown", |
733 | | - discussion.createdAt |
| 802 | + discussion.createdAt, |
| 803 | + discussion.poll || null |
734 | 804 | ); |
735 | 805 |
|
736 | 806 | createdDiscussions++; |
737 | 807 | log(`✓ Created discussion #${discussion.number}: '${discussion.title}'`); |
738 | 808 |
|
| 809 | + // Log poll info if present |
| 810 | + if (discussion.poll && discussion.poll.options?.nodes?.length > 0) { |
| 811 | + log(` ℹ️ Poll included with ${discussion.poll.options.nodes.length} options (${discussion.poll.totalVoteCount} total votes)`); |
| 812 | + } |
| 813 | + |
739 | 814 | // Process labels |
740 | 815 | if (discussion.labels.nodes.length > 0) { |
741 | 816 | const labelIds = []; |
|
0 commit comments