Talk Proposal: #81
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Speaker Date Selection | |
| on: | |
| issues: | |
| types: [opened, labeled] | |
| jobs: | |
| comment-dates: | |
| runs-on: ubuntu-latest | |
| if: | | |
| (github.event.action == 'labeled' && (contains(github.event.issue.labels.*.name, 'Talk') || contains(github.event.issue.labels.*.name, 'Demo'))) | |
| env: | |
| SPEAKER_DATE_SELECTION_ENABLED: ${{ vars.SPEAKER_DATE_SELECTION_ENABLED || 'true' }} | |
| steps: | |
| - name: Check if automation is enabled | |
| if: env.SPEAKER_DATE_SELECTION_ENABLED != 'true' | |
| run: | | |
| echo "π« Speaker date selection is disabled" | |
| exit 0 | |
| - name: Get milestones and post date options | |
| if: env.SPEAKER_DATE_SELECTION_ENABLED == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const BOT_MARKER = '<!-- speaker-date-selection-bot -->'; | |
| // Check if comment already exists to prevent duplicates | |
| const existingComments = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number: context.issue.number | |
| }); | |
| // Look for our bot comments | |
| const botComments = existingComments.data.filter(comment => | |
| comment.user.type === 'Bot' && | |
| (comment.body.includes(BOT_MARKER) || | |
| comment.body.includes('Available Presentation Dates')) | |
| ); | |
| let isUpdate = false; | |
| if (botComments.length > 0) { | |
| // For 'opened' events, never allow duplicates | |
| if (context.payload.action === 'opened') { | |
| console.log('βοΈ Date selection comment already exists on new issue, skipping'); | |
| return; | |
| } | |
| // For 'labeled' events (Event label), check if last comment was within 5 minutes | |
| const lastBotComment = botComments[botComments.length - 1]; | |
| const lastCommentTime = new Date(lastBotComment.created_at); | |
| const now = new Date(); | |
| const minutesSinceLastComment = (now - lastCommentTime) / (1000 * 60); | |
| if (minutesSinceLastComment < 5) { | |
| console.log(`β° Last comment was ${minutesSinceLastComment.toFixed(1)} minutes ago, skipping (minimum 5 minutes)`); | |
| return; | |
| } | |
| console.log(`β Last comment was ${minutesSinceLastComment.toFixed(1)} minutes ago, proceeding with update`); | |
| isUpdate = true; | |
| } | |
| // Get all open milestones and all open issues with Talk/Demo labels in parallel | |
| const [milestonesResponse, issuesResponse] = await Promise.all([ | |
| github.rest.issues.listMilestones({ | |
| owner, | |
| repo, | |
| state: 'open', | |
| per_page: 100 | |
| }), | |
| github.rest.issues.listForRepo({ | |
| owner, | |
| repo, | |
| state: 'open', | |
| labels: 'Talk', | |
| per_page: 100 | |
| }) | |
| ]); | |
| // Also get Demo labeled issues | |
| const demoIssuesResponse = await github.rest.issues.listForRepo({ | |
| owner, | |
| repo, | |
| state: 'open', | |
| labels: 'Demo', | |
| per_page: 100 | |
| }); | |
| // Combine Talk and Demo issues, removing duplicates | |
| const allTalkDemoIssues = [...issuesResponse.data, ...demoIssuesResponse.data]; | |
| const uniqueIssues = Array.from( | |
| new Map(allTalkDemoIssues.map(issue => [issue.id, issue])).values() | |
| ); | |
| // Count issues per milestone | |
| const issueCounts = {}; | |
| for (const issue of uniqueIssues) { | |
| if (issue.milestone) { | |
| issueCounts[issue.milestone.number] = (issueCounts[issue.milestone.number] || 0) + 1; | |
| } | |
| } | |
| console.log(`π Found ${milestonesResponse.data.length} open milestones`); | |
| // Filter eligible milestones | |
| const eligibleMilestones = []; | |
| const now = new Date(); | |
| // Add 180 days instead of 6 months to avoid month overflow issues | |
| const sixMonthsFromNow = new Date(now.getTime() + (180 * 24 * 60 * 60 * 1000)); | |
| for (const milestone of milestonesResponse.data) { | |
| // Skip milestones without due dates or that don't end with 'Meetup' | |
| if (!milestone.due_on || !milestone.title.endsWith('Meetup')) { | |
| continue; | |
| } | |
| // Only include future dates within next 6 months | |
| const dueDate = new Date(milestone.due_on); | |
| if (dueDate > now && dueDate <= sixMonthsFromNow) { | |
| const talkDemoCount = issueCounts[milestone.number] || 0; | |
| if (talkDemoCount < 3) { | |
| eligibleMilestones.push({ | |
| title: milestone.title, | |
| due_on: milestone.due_on, | |
| open_issues: talkDemoCount | |
| }); | |
| } | |
| } | |
| } | |
| console.log(`β¨ Found ${eligibleMilestones.length} eligible milestones`); | |
| if (eligibleMilestones.length === 0) { | |
| console.log('β οΈ No eligible milestones found'); | |
| // Post a message indicating no dates are available | |
| const updateTime = new Date().toISOString(); | |
| let noDateBody = `${BOT_MARKER}\n<!-- updated: ${updateTime} -->\nβ οΈ No available meetup dates found. Please contact the organizers to schedule your presentation.`; | |
| if (isUpdate) { | |
| noDateBody += "\n\n<sub>β¨ Checked at " + new Date().toLocaleString('en-US', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| month: 'short', | |
| day: 'numeric', | |
| year: 'numeric' | |
| }) + "</sub>"; | |
| } | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: context.issue.number, | |
| body: noDateBody | |
| }); | |
| return; | |
| } | |
| // Sort by due date | |
| eligibleMilestones.sort((a, b) => new Date(a.due_on) - new Date(b.due_on)); | |
| // Create comment body with checkboxes | |
| const updateTime = new Date().toISOString(); | |
| let commentBody = `${BOT_MARKER}\n<!-- updated: ${updateTime} -->\n## π Available Presentation Dates\n\nSelect your preferred dates for your talk:\n\n`; | |
| for (const milestone of eligibleMilestones) { | |
| const date = new Date(milestone.due_on).toLocaleDateString('en-US', { | |
| weekday: 'long', | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| const spotsLeft = 3 - milestone.open_issues; | |
| commentBody += `- [ ] **${date}** (${milestone.title}) - ${spotsLeft} spot${spotsLeft !== 1 ? 's' : ''} available\n`; | |
| } | |
| commentBody += "\n---\n*Please check the boxes next to your preferred dates. A maintainer will assign you to a meetup once you've made your selections.*"; | |
| // Add update notice if this is a re-run | |
| if (isUpdate) { | |
| commentBody += "\n\n<sub>β¨ Updated availability at " + new Date().toLocaleString('en-US', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| month: 'short', | |
| day: 'numeric', | |
| year: 'numeric' | |
| }) + "</sub>"; | |
| } | |
| // Post comment on the issue | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: context.issue.number, | |
| body: commentBody | |
| }); | |
| console.log('β Posted date selection comment'); |