Skip to content

Talk Proposal:

Talk Proposal: #81

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');