Skip to content

Commit 7a25889

Browse files
committed
Add Google Sheets integration with service account authentication
- Set up secure service account authentication for Google Sheets - Created Python script to fetch workshop data - Added GitHub Actions workflow for daily updates - Updated workshop cards to show active status and registration buttons - Implemented sorting with active workshops displayed first - Created new 'Available Now' page for currently scheduled workshops - Added navigation and homepage integration
1 parent 459010f commit 7a25889

23 files changed

+814
-141
lines changed

.DS_Store

-10 KB
Binary file not shown.

.claude/settings.local.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(find:*)",
5+
"Bash(ls:*)",
6+
"Bash(mkdir:*)",
7+
"Bash(bundle install:*)",
8+
"Bash(bundle exec jekyll serve:*)",
9+
"Bash(bundle exec jekyll:*)",
10+
"Bash(curl:*)",
11+
"Bash(python:*)",
12+
"Bash(git add:*)",
13+
"Bash(git commit:*)",
14+
"Bash(git push:*)",
15+
"WebFetch(domain:dlab-berkeley.github.io)",
16+
"Bash(git add static/stylesheets/custom-home.css)",
17+
"Bash(git commit -m \"Fix banner overlapping with navbar\n\n- Add 80px top margin to homepage-hero to push banner below fixed navbar\n- Prevents banner from being hidden behind navigation menu\n\n🤖 Generated with [Claude Code](https://claude.ai/code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\")",
18+
"WebFetch(domain:dlab.my.salesforce-sites.com)",
19+
"Bash(chmod:*)"
20+
],
21+
"deny": []
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Update Workshop Data from Google Sheets
2+
3+
on:
4+
schedule:
5+
# Run daily at 8 AM UTC (1 AM PST)
6+
- cron: '0 8 * * *'
7+
workflow_dispatch: # Allow manual triggering
8+
9+
jobs:
10+
update-workshops:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v3
16+
with:
17+
token: ${{ secrets.GITHUB_TOKEN }}
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version: '3.9'
23+
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
28+
29+
- name: Fetch workshop data from Google Sheets
30+
env:
31+
GOOGLE_SHEET_ID: ${{ secrets.GOOGLE_SHEET_ID }}
32+
GOOGLE_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_KEY }}
33+
run: |
34+
python scripts/fetch_google_sheets.py
35+
36+
- name: Commit and push changes
37+
run: |
38+
git config --local user.email "action@github.com"
39+
git config --local user.name "GitHub Action"
40+
git add _data/upcoming_workshops.json
41+
if git diff --staged --quiet; then
42+
echo "No changes to commit"
43+
else
44+
git commit -m "Update upcoming workshops data [skip ci]"
45+
git push
46+
fi

GOOGLE_SHEETS_SETUP.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Google Sheets Integration Setup
2+
3+
This document explains how to set up and use the Google Sheets integration for displaying active workshops on the D-Lab workshops website.
4+
5+
## Overview
6+
7+
The website automatically fetches data from a Google Sheet daily to display currently active workshops with registration information. Active workshops are highlighted in the catalog and sorted to appear first.
8+
9+
## Google Sheet Format
10+
11+
Your Google Sheet should have the following columns:
12+
13+
| Column | Description | Example |
14+
|--------|-------------|---------|
15+
| title | Workshop title (must match catalog) | Python Fundamentals: Parts 1-3 |
16+
| date | Workshop date (MM/DD/YYYY) | 12/15/2025 |
17+
| time | Workshop time (HH:MM AM/PM) | 2:00 PM |
18+
| registration_url | Link to registration | https://berkeley.zoom.us/meeting/register/... |
19+
| instructor | Instructor name | Jane Doe |
20+
| location | Workshop location | Online |
21+
| description | Optional description | Learn Python basics in this 3-part series |
22+
23+
## Setup Instructions
24+
25+
### 1. Create Your Google Sheet
26+
27+
1. Create a new Google Sheet with the columns listed above
28+
2. Make the sheet publicly readable:
29+
- Click "Share" button
30+
- Click "Change to anyone with the link"
31+
- Set permission to "Viewer"
32+
- Copy the sharing link
33+
34+
### 2. Get Your Sheet ID
35+
36+
From the Google Sheets URL:
37+
```
38+
https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID_HERE/edit#gid=0
39+
```
40+
41+
The Sheet ID is the part between `/d/` and `/edit`.
42+
43+
### 3. Configure GitHub Repository
44+
45+
1. Go to your repository Settings → Secrets and variables → Actions
46+
2. Add a new repository secret:
47+
- Name: `GOOGLE_SHEET_ID`
48+
- Value: Your Google Sheet ID
49+
50+
### 4. Enable GitHub Actions
51+
52+
The workflow will run automatically:
53+
- Daily at 8 AM UTC (1 AM PST)
54+
- Can be triggered manually from Actions tab
55+
56+
## How It Works
57+
58+
1. **Data Fetching**: The Python script fetches data from your Google Sheet
59+
2. **Data Processing**: Workshops are parsed and sorted by date
60+
3. **JSON Update**: The `_data/upcoming_workshops.json` file is updated
61+
4. **Website Display**: JavaScript dynamically updates workshop cards to show:
62+
- Green border highlight for active workshops
63+
- "Next session" badge with date
64+
- Register button linking to registration URL
65+
- Active workshops sorted first in category pages
66+
67+
## Features
68+
69+
### Workshop Highlighting
70+
Active workshops get:
71+
- Green border and shadow
72+
- Session date badge
73+
- Registration button
74+
75+
### Sorting
76+
Workshop lists show:
77+
- "Currently Available" section first
78+
- "Workshop Catalog" section for inactive workshops
79+
80+
### Available Now Page
81+
A dedicated page (`/available-now`) shows only active workshops with:
82+
- Full schedule table
83+
- Registration links
84+
- Session details
85+
86+
## Troubleshooting
87+
88+
### Workshops Not Showing as Active
89+
90+
1. **Check title matching**: Workshop titles in the Google Sheet must exactly match those in `_data/workshops.yml`
91+
2. **Check date format**: Dates must be in MM/DD/YYYY format
92+
3. **Check GitHub Actions**: Ensure the workflow is running successfully
93+
94+
### Manual Update
95+
96+
To manually trigger an update:
97+
1. Go to Actions tab in GitHub
98+
2. Select "Update Workshop Data from Google Sheets"
99+
3. Click "Run workflow"
100+
101+
## Maintenance
102+
103+
- The Google Sheet should be updated with new workshop sessions as they're scheduled
104+
- Remove past workshops to keep the sheet clean
105+
- The website automatically updates daily, no manual intervention needed

R.html

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,21 @@ <h1 class="mb-4">R Workshops</h1>
2525
</div>
2626
</div>
2727

28-
<div class="row">
29-
{% assign r_workshops = "" | split: "" %}
30-
{% for workshop in site.data.workshops.workshops %}
31-
{% if workshop.category == 'r' %}
32-
{% assign r_workshops = r_workshops | push: workshop %}
33-
{% elsif workshop.category contains 'r' and workshop.category.size > 1 %}
34-
{% for cat in workshop.category %}
35-
{% if cat == 'r' %}
36-
{% assign r_workshops = r_workshops | push: workshop %}
37-
{% break %}
38-
{% endif %}
39-
{% endfor %}
40-
{% endif %}
41-
{% endfor %}
42-
43-
{% comment %} Sort by difficulty: introductory, intermediate, advanced {% endcomment %}
44-
{% assign introductory_workshops = r_workshops | where: "level", "introductory" %}
45-
{% assign intermediate_workshops = r_workshops | where: "level", "intermediate" %}
46-
{% assign advanced_workshops = r_workshops | where: "level", "advanced" %}
47-
48-
{% for workshop in introductory_workshops %}
49-
{% include workshop-card.html workshop=workshop %}
50-
{% endfor %}
51-
{% for workshop in intermediate_workshops %}
52-
{% include workshop-card.html workshop=workshop %}
53-
{% endfor %}
54-
{% for workshop in advanced_workshops %}
55-
{% include workshop-card.html workshop=workshop %}
56-
{% endfor %}
57-
</div>
28+
{% assign r_workshops = "" | split: "" %}
29+
{% for workshop in site.data.workshops.workshops %}
30+
{% if workshop.category == 'r' %}
31+
{% assign r_workshops = r_workshops | push: workshop %}
32+
{% elsif workshop.category contains 'r' and workshop.category.size > 1 %}
33+
{% for cat in workshop.category %}
34+
{% if cat == 'r' %}
35+
{% assign r_workshops = r_workshops | push: workshop %}
36+
{% break %}
37+
{% endif %}
38+
{% endfor %}
39+
{% endif %}
40+
{% endfor %}
41+
42+
{% include workshop-list-sorted.html workshops=r_workshops %}
5843

5944
<div class="row mt-4">
6045
<div class="col-12">

_includes/navbar.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
{% if page.layout == "index" %}
1919
<li class="nav-item">
2020
<a class="nav-link active" href="#">Home</a>
21+
<li class="nav-item">
22+
<a class="nav-link" href="available-now">Available Now</a>
23+
</li>
2124
<li class="nav-item">
2225
<a class="nav-link" href="guide">Guide</a>
2326
</li>
@@ -30,6 +33,9 @@
3033
{% elsif page.layout == "guide" %}
3134
<li class="nav-item">
3235
<a class="nav-link" href="../">Home</a>
36+
<li class="nav-item">
37+
<a class="nav-link" href="../available-now">Available Now</a>
38+
</li>
3339
<li class="nav-item">
3440
<a class="nav-link active" href="../guide">Guide</a>
3541
</li>
@@ -42,6 +48,9 @@
4248
{% elsif page.layout == "library" %}
4349
<li class="nav-item">
4450
<a class="nav-link" href="../">Home</a>
51+
<li class="nav-item">
52+
<a class="nav-link" href="../available-now">Available Now</a>
53+
</li>
4554
<li class="nav-item">
4655
<a class="nav-link" href="../guide">Guide</a>
4756
</li>
@@ -51,9 +60,27 @@
5160
<li class="nav-item">
5261
<a class="nav-link" href="../about">About</a>
5362
</li>
63+
{% elsif page.url == "/available-now" %}
64+
<li class="nav-item">
65+
<a class="nav-link" href="../">Home</a>
66+
<li class="nav-item">
67+
<a class="nav-link active" href="../available-now">Available Now</a>
68+
</li>
69+
<li class="nav-item">
70+
<a class="nav-link" href="../guide">Guide</a>
71+
</li>
72+
<li class="nav-item">
73+
<a class="nav-link" href="../library">Library</a>
74+
</li>
75+
<li class="nav-item">
76+
<a class="nav-link" href="../about">About</a>
77+
</li>
5478
{% elsif page.layout == "about" %}
5579
<li class="nav-item">
5680
<a class="nav-link" href="../">Home</a>
81+
<li class="nav-item">
82+
<a class="nav-link" href="../available-now">Available Now</a>
83+
</li>
5784
<li class="nav-item">
5885
<a class="nav-link" href="../guide">Guide</a>
5986
</li>
@@ -66,6 +93,9 @@
6693
{% else %}
6794
<li class="nav-item">
6895
<a class="nav-link" href="../">Home</a>
96+
<li class="nav-item">
97+
<a class="nav-link" href="../available-now">Available Now</a>
98+
</li>
6999
<li class="nav-item">
70100
<a class="nav-link" href="../guide">Guide</a>
71101
</li>

_includes/workshop-card.html

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
<div class="col-md-6 col-lg-4 mb-4">
2-
<div class="card h-100 workshop-card border-0 shadow-sm">
2+
<div class="card h-100 workshop-card border-0 shadow-sm" data-workshop-title="{{ workshop.title }}">
33
<div class="card-body d-flex flex-column">
44
<div class="d-flex justify-content-between align-items-start mb-2">
55
<h5 class="card-title mb-0">{{ workshop.title }}</h5>
6-
{% if workshop.level == 'introductory' %}
7-
<span class="badge badge-pill badge-success">
8-
<i class="fas fa-seedling"></i> Intro
9-
</span>
10-
{% elsif workshop.level == 'intermediate' %}
11-
<span class="badge badge-pill badge-warning">
12-
<i class="fas fa-layer-group"></i> Intermediate
13-
</span>
14-
{% elsif workshop.level == 'advanced' %}
15-
<span class="badge badge-pill badge-danger">
16-
<i class="fas fa-rocket"></i> Advanced
17-
</span>
18-
{% endif %}
6+
<div class="d-flex flex-column align-items-end">
7+
{% if workshop.level == 'introductory' %}
8+
<span class="badge badge-pill badge-success mb-1">
9+
<i class="fas fa-seedling"></i> Intro
10+
</span>
11+
{% elsif workshop.level == 'intermediate' %}
12+
<span class="badge badge-pill badge-warning mb-1">
13+
<i class="fas fa-layer-group"></i> Intermediate
14+
</span>
15+
{% elsif workshop.level == 'advanced' %}
16+
<span class="badge badge-pill badge-danger mb-1">
17+
<i class="fas fa-rocket"></i> Advanced
18+
</span>
19+
{% endif %}
20+
<div class="upcoming-session-badge"></div>
21+
</div>
1922
</div>
2023

2124
{% if workshop.parts %}
@@ -35,6 +38,7 @@ <h5 class="card-title mb-0">{{ workshop.title }}</h5>
3538
{% endif %}
3639

3740
<div class="workshop-links mt-auto">
41+
<div class="register-button-container mb-2"></div>
3842
<a href="{{ workshop.github_url }}" class="btn btn-sm btn-dark" target="_blank">
3943
<i class="fab fa-github"></i> GitHub
4044
</a>

0 commit comments

Comments
 (0)