Skip to content

Commit 79d0b95

Browse files
committed
Add quotes sync functionality to Google Sheets integration
1 parent f8f8777 commit 79d0b95

3 files changed

Lines changed: 224 additions & 10 deletions

File tree

.github/workflows/sync-team-data.yml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Sync Team Data from Google Sheets
1+
name: Sync Data from Google Sheets
22

33
on:
44
schedule:
@@ -26,9 +26,10 @@ jobs:
2626
pip install google-auth google-auth-oauthlib google-auth-httplib2
2727
pip install google-api-python-client PyYAML
2828
29-
- name: Sync from Google Sheets
29+
- name: Sync Team Data from Google Sheets
3030
env:
3131
SERVICE_ACCOUNT_KEY: ${{ secrets.SERVICE_ACCOUNT_KEY }}
32+
TEAM_SPREADSHEET_ID: ${{ secrets.TEAM_SPREADSHEET_ID }}
3233
run: |
3334
# Debug: Check if secret is set
3435
if [ -z "$SERVICE_ACCOUNT_KEY" ]; then
@@ -42,19 +43,34 @@ jobs:
4243
# Debug: Check file was created
4344
ls -la service-account-key.json
4445
45-
# Run the sync script
46+
# Run the team sync script
4647
python sync_team_service_account.py
4748
49+
- name: Sync Quotes from Google Sheets
50+
env:
51+
SERVICE_ACCOUNT_KEY: ${{ secrets.SERVICE_ACCOUNT_KEY }}
52+
QUOTES_SPREADSHEET_ID: ${{ secrets.QUOTES_SPREADSHEET_ID }}
53+
run: |
54+
# Update the quotes sync script with the spreadsheet ID
55+
if [ -n "$QUOTES_SPREADSHEET_ID" ]; then
56+
sed -i "s/YOUR_QUOTES_SPREADSHEET_ID_HERE/$QUOTES_SPREADSHEET_ID/g" sync_quotes_from_sheets.py
57+
58+
# Run the quotes sync script
59+
python sync_quotes_from_sheets.py
60+
else
61+
echo "QUOTES_SPREADSHEET_ID not set, skipping quotes sync"
62+
fi
63+
4864
- name: Check for changes
4965
id: verify-changed-files
5066
run: |
51-
git diff --quiet _data/*.yml || echo "changed=true" >> $GITHUB_OUTPUT
67+
git diff --quiet _data/*.yml _pages/quotes.md || echo "changed=true" >> $GITHUB_OUTPUT
5268
5369
- name: Commit and push if changed
5470
if: steps.verify-changed-files.outputs.changed == 'true'
5571
run: |
5672
git config user.name "GitHub Actions"
5773
git config user.email "actions@github.com"
58-
git add _data/*.yml
59-
git commit -m "Update team data from Google Sheets"
74+
git add _data/*.yml _pages/quotes.md
75+
git commit -m "Update data from Google Sheets"
6076
git push

SETUP_GOOGLE_SHEETS_SYNC.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Google Sheets Team Sync Setup Guide
1+
# Google Sheets Data Sync Setup Guide
22

33
## Using Service Account (Recommended)
44

@@ -29,14 +29,24 @@
2929
5. Click **Send**
3030

3131
### Step 5: Run the Sync
32+
33+
For team data:
3234
```bash
3335
cd "/Users/bwestove/cdac Dropbox/brandon westover/0_GithubRepos/bdsp-core.github.io"
3436
source venv/bin/activate
3537
python sync_team_service_account.py
3638
```
3739

38-
## Google Sheet Format
39-
Your sheet should have these columns:
40+
For quotes:
41+
```bash
42+
# First, update the SPREADSHEET_ID in sync_quotes_from_sheets.py
43+
python sync_quotes_from_sheets.py
44+
```
45+
46+
## Google Sheet Formats
47+
48+
### Team Sheet Format
49+
Your team sheet should have these columns:
4050
- Name
4151
- Link (optional URL)
4252
- Photo (filename)
@@ -48,5 +58,23 @@ Your sheet should have these columns:
4858
- Education4
4959
- Category (must be one of: Faculty, Alumni, Postdocs/Students/Staff, Collaborators)
5060

61+
### Quotes Sheet Format
62+
Your quotes sheet should have these columns:
63+
- Section (e.g., "Absurdity", "Academics", "Science", etc.)
64+
- Quote (the actual quote text)
65+
- Attribution (author name and any additional info)
66+
5167
## Automation with GitHub Actions
52-
Once the manual sync works, the GitHub Action will use the same service account to sync automatically.
68+
69+
The GitHub Action will sync both team data and quotes automatically. You need to set these secrets in your repository:
70+
71+
1. **SERVICE_ACCOUNT_KEY**: The contents of your service-account-key.json file
72+
2. **TEAM_SPREADSHEET_ID**: The ID of your team Google Sheet (optional, uses hardcoded ID if not set)
73+
3. **QUOTES_SPREADSHEET_ID**: The ID of your quotes Google Sheet (optional, skips quotes sync if not set)
74+
75+
To set secrets:
76+
1. Go to your repository Settings → Secrets and variables → Actions
77+
2. Click "New repository secret"
78+
3. Add each secret with the appropriate value
79+
80+
The workflow runs daily at 2 AM UTC or can be manually triggered from the Actions tab.

sync_quotes_from_sheets.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Sync quotes from Google Sheets to Jekyll markdown file.
4+
5+
This script reads quotes from a Google Sheet and generates the quotes.md file.
6+
"""
7+
8+
import os
9+
from google.oauth2 import service_account
10+
from googleapiclient.discovery import build
11+
12+
# Scopes for read-only access
13+
SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly']
14+
15+
# Your Google Sheets ID (update this with your quotes sheet ID)
16+
SPREADSHEET_ID = 'YOUR_QUOTES_SPREADSHEET_ID_HERE'
17+
18+
# Sheet name and range
19+
RANGE_NAME = 'Sheet1!A:C' # Assumes columns: Section, Quote, Attribution
20+
21+
def authenticate_google_sheets():
22+
"""Authenticate using service account and return Google Sheets service."""
23+
creds = service_account.Credentials.from_service_account_file(
24+
'service-account-key.json', scopes=SCOPES)
25+
26+
return build('sheets', 'v4', credentials=creds)
27+
28+
def fetch_sheet_data(service):
29+
"""Fetch data from Google Sheets."""
30+
sheet = service.spreadsheets()
31+
result = sheet.values().get(spreadsheetId=SPREADSHEET_ID,
32+
range=RANGE_NAME).execute()
33+
values = result.get('values', [])
34+
35+
if not values:
36+
print('No data found.')
37+
return []
38+
39+
# First row is headers
40+
headers = values[0]
41+
data = []
42+
43+
for row in values[1:]:
44+
# Pad row to match header length
45+
row_data = row + [''] * (len(headers) - len(row))
46+
data.append(dict(zip(headers, row_data)))
47+
48+
return data
49+
50+
def escape_markdown(text):
51+
"""Escape special characters for markdown."""
52+
# Don't escape characters inside quotes
53+
return text
54+
55+
def generate_quotes_markdown(quotes_data):
56+
"""Generate the quotes.md file content from quotes data."""
57+
# Group quotes by section
58+
sections = {}
59+
for quote in quotes_data:
60+
section = quote.get('Section', 'Uncategorized')
61+
if section not in sections:
62+
sections[section] = []
63+
sections[section].append(quote)
64+
65+
# Sort sections alphabetically
66+
sorted_sections = sorted(sections.keys())
67+
68+
# Generate markdown content
69+
content = []
70+
content.append("""---
71+
title: "Brandon - quotes"
72+
layout: textlay
73+
excerpt: "Quote collection"
74+
sitemap: false
75+
permalink: /quotes/
76+
---
77+
78+
<style>
79+
/* this is kind of a hack */
80+
blockquote {
81+
padding: 5px 20px;
82+
margin: 0 0 20px;
83+
font-size: 16px;
84+
border-left: 5px solid #eee;
85+
}
86+
blockquote strong em {
87+
color: #7F8C8D;
88+
}
89+
</style>
90+
91+
# Table of Contents""")
92+
93+
# Generate table of contents
94+
toc_items = []
95+
for section in sorted_sections:
96+
# Create anchor-friendly section ID
97+
section_id = section.lower().replace(' ', '-').replace('/', '-').replace('\'', '')
98+
toc_items.append(f"[{section} | ](#{section_id})")
99+
100+
content.append(' | '.join(toc_items[:5])) # First row
101+
for i in range(5, len(toc_items), 5):
102+
content.append(' | '.join(toc_items[i:i+5]))
103+
104+
content.append("\n[Back to Brandon's page](/brandon/)")
105+
106+
# Generate sections with quotes
107+
for section in sorted_sections:
108+
# Create anchor-friendly section ID
109+
section_id = section.lower().replace(' ', '-').replace('/', '-').replace('\'', '')
110+
111+
content.append(f"\n### {section}")
112+
113+
for quote in sections[section]:
114+
quote_text = quote.get('Quote', '').strip()
115+
attribution = quote.get('Attribution', '').strip()
116+
117+
if quote_text:
118+
# Format the quote
119+
content.append(f"> {quote_text} ")
120+
if attribution:
121+
content.append(f"> **--_{attribution}_**")
122+
content.append("") # Empty line between quotes
123+
124+
content.append("[Back to Top](# )\n")
125+
content.append("\n[Back to Brandon's page](/brandon/)")
126+
127+
return '\n'.join(content)
128+
129+
def save_quotes_file(content):
130+
"""Save the generated content to quotes.md."""
131+
output_path = '_pages/quotes.md'
132+
133+
# Ensure directory exists
134+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
135+
136+
with open(output_path, 'w', encoding='utf-8') as f:
137+
f.write(content)
138+
139+
print(f"Saved quotes to {output_path}")
140+
141+
def main():
142+
"""Main function to sync quotes from Google Sheets."""
143+
# Check if service account key exists
144+
if not os.path.exists('service-account-key.json'):
145+
print("Error: service-account-key.json not found!")
146+
print("Please ensure you're using the same service account as for team sync.")
147+
return
148+
149+
print("Authenticating with Google Sheets...")
150+
service = authenticate_google_sheets()
151+
152+
print("Fetching quotes from Google Sheets...")
153+
quotes_data = fetch_sheet_data(service)
154+
155+
if not quotes_data:
156+
print("No quotes to process.")
157+
return
158+
159+
print(f"Found {len(quotes_data)} quotes")
160+
161+
# Generate markdown content
162+
markdown_content = generate_quotes_markdown(quotes_data)
163+
164+
# Save to file
165+
save_quotes_file(markdown_content)
166+
167+
print("Quotes sync complete!")
168+
169+
if __name__ == '__main__':
170+
main()

0 commit comments

Comments
 (0)