Skip to content

[REGISTER] GIT Going with GitHub - March 2026 #4

[REGISTER] GIT Going with GitHub - March 2026

[REGISTER] GIT Going with GitHub - March 2026 #4

Workflow file for this run

name: Registration - Welcome & CSV Export
on:
issues:
types: [opened]
workflow_dispatch:
permissions:
issues: write
jobs:
welcome:
name: Welcome New Registrant
runs-on: ubuntu-latest
if: >-
github.event_name == 'issues' &&
github.event.action == 'opened' &&
contains(github.event.issue.title, '[REGISTER]')
steps:
- name: Check for duplicate registration
id: dup-check
uses: actions/github-script@v7
with:
script: |
const username = context.payload.issue.user.login;
const currentIssueNumber = context.issue.number;
// Find existing registration issues by this user
const existingIssues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'registration',
state: 'all',
per_page: 100
});
const priorRegistration = existingIssues.find(
issue => issue.user.login === username && issue.number !== currentIssueNumber
);
if (priorRegistration) {
core.setOutput('is_duplicate', 'true');
core.setOutput('original_issue', priorRegistration.number);
} else {
core.setOutput('is_duplicate', 'false');
}
- name: Handle duplicate registration
if: steps.dup-check.outputs.is_duplicate == 'true'
uses: actions/github-script@v7
with:
script: |
const originalIssue = ${{ steps.dup-check.outputs.original_issue }};
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## You are already registered!
Hi @${context.payload.issue.user.login}, it looks like you already registered in issue #${originalIssue}. No need to register again — your spot is saved!
We have closed this issue since your original registration is already on file. If you need to update your details, please comment on your original issue #${originalIssue}.
Questions? Email [support@bits-acb.org](mailto:support@bits-acb.org).`
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['duplicate']
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned'
});
- name: Check capacity
id: capacity-check
if: steps.dup-check.outputs.is_duplicate == 'false'
uses: actions/github-script@v7
with:
script: |
const MAX_CAPACITY = 75;
// Count unique registered users (excluding this issue)
const existingIssues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'registration',
state: 'all',
per_page: 100
});
const currentIssueNumber = context.issue.number;
const uniqueUsers = new Set();
for (const issue of existingIssues) {
if (issue.number !== currentIssueNumber) {
uniqueUsers.add(issue.user.login);
}
}
const currentCount = uniqueUsers.size;
core.setOutput('count', currentCount);
if (currentCount >= MAX_CAPACITY) {
core.setOutput('is_full', 'true');
} else {
core.setOutput('is_full', 'false');
}
- name: Handle registration full
if: steps.dup-check.outputs.is_duplicate == 'false' && steps.capacity-check.outputs.is_full == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## Registration is currently full
Hi @${context.payload.issue.user.login}, thank you for your interest in GIT Going with GitHub! Unfortunately, all 75 spots have been filled.
We have added you to the waitlist. If a spot opens up, we will let you know right here on this issue.
Questions? Email [support@bits-acb.org](mailto:support@bits-acb.org).`
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['waitlist']
});
- name: Post welcome comment
if: steps.dup-check.outputs.is_duplicate == 'false' && steps.capacity-check.outputs.is_full == 'false'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `## Welcome to GIT Going with GitHub!
Hi @${context.payload.issue.user.login}, thank you for registering! We are excited to have you join us.
Your registration is confirmed. Please register for the Zoom session here: [Register on Zoom](https://us06web.zoom.us/meeting/register/YdAAvwzAQUCYpPpNtAlG3g)
In the meantime, you can get a head start with the [Pre-Workshop Setup Guide](https://bits-acb.github.io/git-going-with-github/docs/00-pre-workshop-setup.html).
If you have any questions, email us at [support@bits-acb.org](mailto:support@bits-acb.org).
See you on March 7!`
});
- name: Add registration label
if: steps.dup-check.outputs.is_duplicate == 'false' && steps.capacity-check.outputs.is_full == 'false'
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['registration']
});
} catch (e) {
console.log('Label may already exist:', e.message);
}
export-csv:
name: Export Registrations to CSV
runs-on: ubuntu-latest
if: >-
(github.event_name == 'workflow_dispatch') ||
(github.event_name == 'issues' &&
github.event.action == 'opened' &&
contains(github.event.issue.title, '[REGISTER]'))
steps:
- name: Generate CSV from registration issues
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Fetch all issues with the registration label
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'registration',
state: 'all',
per_page: 100
});
// Parse issue body fields (GitHub YAML forms use ### heading + content pattern)
function parseField(body, fieldName) {
if (!body) return '';
// Match the pattern: ### Field Name\n\nValue
// Also handle ### Field Name\r\n\r\nValue
const regex = new RegExp(`### ${fieldName}\\s*\\n+([\\s\\S]*?)(?=\\n### |$)`, 'i');
const match = body.match(regex);
if (!match) return '';
let value = match[1].trim();
// Remove _No response_ placeholder
if (value === '_No response_') return '';
// Escape CSV: quote fields containing commas, quotes, or newlines
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
value = '"' + value.replace(/"/g, '""') + '"';
}
return value;
}
// Build CSV
const headers = [
'Registration Date',
'GitHub Username',
'First Name',
'Last Name',
'Email Address',
'GitHub Proficiency Level',
'Primary Screen Reader',
'Questions or Accommodations',
'Issue Number',
'Issue URL'
];
let csv = headers.join(',') + '\n';
// Deduplicate by GitHub username, keeping only the earliest registration
const seen = new Set();
const sortedIssues = issues.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
for (const issue of sortedIssues) {
const username = issue.user.login;
if (seen.has(username)) continue;
seen.add(username);
const row = [
issue.created_at.split('T')[0],
username,
parseField(issue.body, 'First Name'),
parseField(issue.body, 'Last Name'),
parseField(issue.body, 'Email Address'),
parseField(issue.body, 'GitHub Proficiency Level'),
parseField(issue.body, 'Primary Screen Reader'),
parseField(issue.body, 'Questions or Accommodations'),
issue.number,
issue.html_url
];
csv += row.join(',') + '\n';
}
// Write CSV file
fs.mkdirSync('.github/data', { recursive: true });
fs.writeFileSync('.github/data/registrations.csv', csv, 'utf-8');
console.log(`Exported ${seen.size} unique registration(s) from ${issues.length} total issue(s) to .github/data/registrations.csv`);
- name: Upload CSV as artifact
uses: actions/upload-artifact@v4
with:
name: registrations
path: .github/data/registrations.csv
retention-days: 90