diff --git a/.github/workflows/add-newsletter-to-archive.yml b/.github/workflows/add-newsletter-to-archive.yml new file mode 100644 index 0000000000..876f3bee04 --- /dev/null +++ b/.github/workflows/add-newsletter-to-archive.yml @@ -0,0 +1,69 @@ +name: "Add Newsletter to Archive" + +on: + workflow_dispatch: + inputs: + marketing_email_id: + description: "The unique identifier in HubSpot for the email you want to import. Find it at the end of a single email's URL or in its details panel." + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + add-newsletter: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 7.0.1 + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # 7.0.0 + with: + python-version-file: .github/workflows/.python-version + + - name: Install dependencies + run: pip install "hubspot-api-client==12.0.0" "requests==2.34.2" "beautifulsoup4==4.15.0" + + - name: Export HTML and images from Hubspot + env: + HUBSPOT_EXPORT_ACCESS_TOKEN: ${{ secrets.HUBSPOT_EXPORT_ACCESS_TOKEN }} + HUBSPOT_INSTANCE_ID: ${{ secrets.HUBSPOT_INSTANCE_ID }} + run: python .github/workflows/add-newsletter-to-archive/fetch-from-hubspot.py ${{ inputs.marketing_email_id }} + + - name: Create Markdown file and replace placeholder strings + run: | + NEW_FILE="docs/reference/newsletter-archive/${NEWSLETTER_SLUG}.md" + cp .github/workflows/add-newsletter-to-archive/shell-page-template.md $NEW_FILE + sed -i "s/{{NEWSLETTER_NICENAME}}/${NEWSLETTER_NICENAME}/g" $NEW_FILE + sed -i "s/{{NEWSLETTER_SLUG}}/${NEWSLETTER_SLUG}/g" $NEW_FILE + sed -i "s/{{NEWSLETTER_YEAR}}/${NEWSLETTER_YEAR}/g" $NEW_FILE + + - name: Add card to index + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0 + with: + script: | + const { addCardToIndex } = await import('${{ github.workspace }}/.github/workflows/add-newsletter-to-archive/add-card-to-index.js'); + addCardToIndex({core}); + + - name: Commit changes and open PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git switch -c "docs/add-newsletter-${NEWSLETTER_SLUG}" + git add . + git commit -m "docs(newsletter): add ${NEWSLETTER_NICENAME} to archive" + git push --set-upstream origin "docs/add-newsletter-${NEWSLETTER_SLUG}" + PR_BODY=.github/workflows/add-newsletter-to-archive/pr-body-template.md + sed -i "s/{{NEWSLETTER_NICENAME}}/${NEWSLETTER_NICENAME}/g" $PR_BODY + sed -i "s/{{NEWSLETTER_SLUG}}/${NEWSLETTER_SLUG}/g" $PR_BODY + gh pr create \ + --title "docs: adds ${NEWSLETTER_NICENAME} to newsletter archive" \ + --body-file $PR_BODY \ + --label "documentation" \ + --assignee indexing \ + --draft diff --git a/.github/workflows/add-newsletter-to-archive/add-card-to-index.js b/.github/workflows/add-newsletter-to-archive/add-card-to-index.js new file mode 100644 index 0000000000..312d5f27f1 --- /dev/null +++ b/.github/workflows/add-newsletter-to-archive/add-card-to-index.js @@ -0,0 +1,49 @@ +import { readFileSync, writeFileSync } from "fs"; + +export const addCardToIndex = ({ core }) => { + const { NEWSLETTER_NICENAME, NEWSLETTER_SLUG } = process.env; + + // Read the current index file + const filePath = "docs/reference/newsletter-archive/index.md"; + let content = readFileSync(filePath, "utf8"); + + // Create the new card lines + const newCard = ` + - ### :material-email-newsletter: ${NEWSLETTER_NICENAME} + + *Subtitle TKTKTK* + + --- + + Summary TKTKTK + + [Read full newsletter →](${NEWSLETTER_SLUG}/)`; + + // Find the current year's section + const lines = content.split("\n"); + let currentYearIdx = -1; + + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes(".insertion-point")) { + currentYearIdx = i; + break; + } + } + + // Insert the new card at the top of the current year section + if (currentYearIdx !== -1) { + lines.splice(currentYearIdx + 1, 0, newCard); + content = lines.join("\n"); + } else { + core.error("Newsletter index insertion point not found."); + core.setFailed(); + } + + // Update the file + try { + writeFileSync(filePath, content); + } catch (err) { + core.error("Error updating newsletter index:", err); + core.setFailed(); + } +}; diff --git a/.github/workflows/add-newsletter-to-archive/fetch-from-hubspot.py b/.github/workflows/add-newsletter-to-archive/fetch-from-hubspot.py new file mode 100644 index 0000000000..a439a758a9 --- /dev/null +++ b/.github/workflows/add-newsletter-to-archive/fetch-from-hubspot.py @@ -0,0 +1,84 @@ +import argparse +import os +import sys +import urllib +from pathlib import Path +from zoneinfo import ZoneInfo + +import requests +from bs4 import BeautifulSoup +from hubspot import HubSpot + +ACCESS_TOKEN = os.environ["HUBSPOT_EXPORT_ACCESS_TOKEN"] + +hubspot = HubSpot(access_token=ACCESS_TOKEN) + + +def scrape_and_store(url, send_time): + # scrape HTML + r = requests.get(url, timeout=30) + soup = BeautifulSoup(r.content, "html.parser") + + # collect images and store in shared images folder (overwriting any with same filename) + for img in soup.find_all("img"): + img_src = img["src"] + img_src_parsed = urllib.parse.urlsplit(img_src) + filename = os.path.basename(img_src_parsed.path) + + # download image if we haven't yet + decoded_filename = urllib.parse.unquote(filename) + download_path = f"docs/reference/newsletter-archive/exports/images/{decoded_filename}" + if not Path(download_path).exists(): + print(f"Downloading {decoded_filename} from {img_src_parsed._replace(query='').geturl()}") + with open(download_path, mode="wb") as file: + img_url = img_src_parsed._replace(query="").geturl() # drop query params to get largest size + img_r = requests.get(img_url, timeout=30) + file.write(img_r.content) + + # replace original src with relative path and drop responsive attrs + img["src"] = f"images/{filename}" + del img["sizes"] + del img["srcset"] + + newsletter_nicename = send_time.strftime("%B %Y") # August 2026 + newsletter_slug = send_time.strftime("%Y-%m") # 2026-08 + newsletter_year = send_time.strftime("%Y") # August 2026 + + # write the updated HTML to a file named YYYY-MM.html + with open(f"docs/reference/newsletter-archive/exports/{newsletter_slug}.html", "w") as file: + file.write(str(soup)) + + return newsletter_nicename, newsletter_slug, newsletter_year + + +def main(argv=None): + argv = argv if argv is not None else sys.argv[1:] + parser = argparse.ArgumentParser( + prog="export.py", + description="Export a HubSpot marketing email's HTML and images, given its ID.", + ) + + parser.add_argument( + "id", + type=str, + help="The HubSpot data type to export (or `all` to get them all).", + ) + + args = parser.parse_args(argv) + + email_response = hubspot.marketing.emails.marketing_emails_api.get_by_id(args.id) + send_time = email_response.publish_date.astimezone(ZoneInfo("America/Los_Angeles")) + nicename, slug, year = scrape_and_store(email_response.webversion.url, send_time) + + # If running inside a GitHub Actions environment, store some data for later use. + if "GITHUB_ENV" in os.environ: + with open(os.environ["GITHUB_ENV"], "a") as env_file: + env_file.write(f"NEWSLETTER_NICENAME={nicename}\n") + env_file.write(f"NEWSLETTER_SLUG={slug}\n") + env_file.write(f"NEWSLETTER_YEAR={year}\n") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/add-newsletter-to-archive/pr-body-template.md b/.github/workflows/add-newsletter-to-archive/pr-body-template.md new file mode 100644 index 0000000000..2843210aa1 --- /dev/null +++ b/.github/workflows/add-newsletter-to-archive/pr-body-template.md @@ -0,0 +1,7 @@ +Adds the **{{NEWSLETTER_NICENAME}}** newsletter to the archive in the docs. + +### Before marking ready for review: + +- [ ] Update subtitle in new index entry (`docs/reference/newsletter-archive/index.md`) +- [ ] Update summary in new index entry +- [ ] Update subtitle in shell page (`docs/reference/newsletter-archive/{{NEWSLETTER_SLUG}}.md`) diff --git a/.github/workflows/add-newsletter-to-archive/shell-page-template.md b/.github/workflows/add-newsletter-to-archive/shell-page-template.md new file mode 100644 index 0000000000..b191c8af9b --- /dev/null +++ b/.github/workflows/add-newsletter-to-archive/shell-page-template.md @@ -0,0 +1,11 @@ +--- +title: "Cal-ITP Benefits Newsletter: {{NEWSLETTER_NICENAME}}" +--- + +[← Back to archive](../#{{NEWSLETTER_YEAR}}_1) + +# :material-email-newsletter: Cal-ITP Benefits Newsletter: {{NEWSLETTER_NICENAME}} + +_Subtitle TKTKTK_ + +