Skip to content

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

[REGISTER] GIT Going with GitHub - March 2026

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

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. We will follow up with the Zoom link and preparation materials by email before the workshop.
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