Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 49 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# veda-github-actions

**A test station for VEDA GitHub Actions + Projects-v2 board seeds.** A safe place to iterate on
Actions and board seed data without touching production. First module: the **FTE capacity report**
(team staffing per Program Increment), so we can experiment with **PI changes** and see the report
diff. Intentionally open — it will grow to host other VEDA Actions experiments.
**A test station for reusable VEDA GitHub Actions, Projects-v2 board seeds, and Netlify dashboards.**
One repo where we iterate safely on reusable Actions, their seed/generator tooling, and the SPAs that
render the output — without touching production. It currently ships **three composite actions**, an
**AWS pricing data generator**, and **five dashboards**. Intentionally open — it grows to host new
VEDA Actions experiments.

## 📋 Project board → https://github.com/users/kyle-lesinger/projects/2

Expand All @@ -14,102 +15,56 @@ diff. Intentionally open — it will grow to host other VEDA Actions experiments
### 🧭 **DSE Hub:** https://veda-dse-hub.netlify.app
### 💵 **AWS Cost Calculator (Disasters Hub):** https://veda-aws-dashboard.netlify.app

## Use this action in another repo
The report is a **reusable composite action** (`action.yml`). Any repo can import it — just point
it at a Projects v2 board and give it a project-read token. Only **two required inputs**; the repo,
owner, and project number are derived:
## Reusable actions
Each action is a **composite** you import with `uses:`. One repo exposes many via subfolders.

```yaml
# .github/workflows/fte.yml in the consuming repo
jobs:
fte:
runs-on: ubuntu-latest
steps:
- uses: NASA-IMPACT/veda-github-actions@v1
id: report
with:
project-url: https://github.com/orgs/YOUR-ORG/projects/5 # your board
token: ${{ secrets.PROJECT_TOKEN_FOR_BOARD_READS }} # PAT: repo+read:org+project (or an App token)
# pi: "PI 27.2" # optional — default is all PIs
# report CSVs + summary are now in ${{ steps.report.outputs.report-dir }} (default: reports/)
```
| Action | Import (`uses:`) | What it does |
|---|---|---|
| **FTE Capacity Report** | `NASA-IMPACT/veda-github-actions@v1` | Reads a board's Objective issues, joins each issue's `## LOE/FTE` table with its PI/date board fields → per-PI CSVs + `fte_summary.md`. |
| **PR Finder** | `NASA-IMPACT/veda-github-actions/pr-finder@v1` | Crawls each Objective's sub-issue tree (5 levels, cross-repo/org) → the PRs that **close** those issues → CSV + Markdown + USWDS HTML. |
| **Leave Tracker** | `NASA-IMPACT/veda-github-actions/leave@v1` | Parses a color-coded leave `.xlsx` (status = cell **fill color**, no text) → `leaves_<slug>.{csv,json}` + coverage. No token/network. |

The action reads the board, joins each Objective's `## LOE/FTE` table with its PI/dates, and writes
PI-slugged `fte_allocations_<slug>.csv` (+ `by_person`, `by_role`, `fte_summary_<slug>.md`; slug = `pi-26.4` or `all-pis`). **Publishing the result is the
caller's choice** — this repo's own [`fte-report.yml`](.github/workflows/fte-report.yml) dogfoods the
action (`uses: ./`) and then publishes to a `fte-report/<pi>` branch + opens a living PR.
- **Generators are stdlib-only Python 3.12 + the `gh` CLI** (no pip deps).
- Board-reading actions need a project-read token — `PROJECT_TOKEN_FOR_BOARD_READS` (classic PAT
`repo+read:org+project`, or an App token). The default `GITHUB_TOKEN` **cannot** read Projects v2.
- Inputs and copy-paste workflow snippets live in each module's doc under [`docs/`](docs/).

## What's here
| Path | Role |
|---|---|
| `action.yml` | **the reusable action** (composite): board → report files. Minimal inputs (`project-url`, `token`). |
| `.github/workflows/fte-report.yml` | thin self-consumer of the action (`uses: ./`) that publishes to `fte-report/<pi>` (+ artifact + PR) |
| `.github/scripts/generate_fte_report.py` | the generator the action runs (Python **stdlib only**) |
| `seed/` | recreate the demo: sample issues → real issues → board fields → populate |
| `fte-dashboard/` | React SPA (Netlify) — Capacity Matrix + what-if editor |
| `docs/FTE_SEED.md` | data model, board fields, gotchas |
| `pr-finder/` | **2nd reusable action** (`uses: .../pr-finder@v1`): crawl objectives' sub-issue trees (5 levels, cross-org) → closing PRs (CSV + MD + USWDS HTML) |
| `.github/workflows/pr-finder.yml` | self-consumer that publishes the PR report to the `pr-finder/report` branch |
| `pr-dashboard/` | static Netlify site rendering the PR report — [live](https://veda-pr-dashboard.netlify.app) |
| `docs/PR_FINDER.md` | PR Finder data model, seed harness, gotchas |
| `leave/` | **3rd reusable action** (`uses: .../leave@v1`): parse a color-coded leave `.xlsx` (status = cell fill) → `leaves_<slug>.{csv,json}` + coverage (stdlib, no token) |
| `.github/workflows/leave-tracker.yml` | self-consumer that publishes the leave report to the `leave-tracker/report` branch |
| `leave-dashboard/` | React SPA (Netlify) — calendar of who's out, person-picker, team-risk heatmap, add-person-via-PR — [live](https://veda-leave-dashboard.netlify.app) |
| `docs/LEAVE_TRACKER.md` | Leave Tracker data model, color map, overrides, gotchas |

## Quickstart

**Offline (no board / no network) — runs the generator on synthetic data:**
```bash
python seed/generate_sample_issues.py
python .github/scripts/generate_fte_report.py \
--issues-json seed/sample_issues.json --out-dir reports --now "$(date -u +%FT%TZ)"
# → reports/fte_allocations_all-pis.csv (+ fte_by_person_all-pis.csv, fte_by_role_all-pis.csv, fte_summary_all-pis.md, fte_manifest.json)
```
## Dashboards & apps
Each dashboard is its **own Netlify site** ("Pattern B": one site per folder, base directory = that
folder, its own `netlify.toml`). Live links are in [APPS](#apps-) above.

**Against the live board (recreate the seed once):**
```bash
python seed/bootstrap_board.py # create the 6 board fields on projects/2
gh label create Objective && gh label create poc-loe
python seed/generate_sample_issues.py
python seed/create_issues.py # create the Objective issues in this repo
python seed/setup_project.py # add to board + set PI / Start / End
python seed/setup_board_grouping.py # set Project / Initiative / Team
```
Then add the token secret and run the Action:
```bash
gh secret set PROJECT_TOKEN_FOR_BOARD_READS --repo NASA-IMPACT/veda-github-actions # classic PAT: repo+read:org+project
gh workflow run fte-report.yml -f pi="All PIs"
```
| App | Folder | What it shows |
|---|---|---|
| FTE Dashboard | [`fte-dashboard/`](fte-dashboard/) | Capacity matrix + what-if PI editor; fetches report CSVs at runtime. |
| PR Finder Dashboard | [`pr-dashboard/`](pr-dashboard/) | Renders accumulated PR-per-Objective reports with a report picker. |
| Leave Dashboard | [`leave-dashboard/`](leave-dashboard/) | Calendar of who's out, person-picker, team-risk heatmap, add-person-via-PR. |
| DSE Hub | [`dse-hub/`](dse-hub/) | Tabbed hub: meeting tracker (list / calendar / categories, timezone-aware) + PI roadmap; edits stage in a Changes cart → one prefilled PR. |
| AWS Cost Calculator | [`cost-dashboard/`](cost-dashboard/) | EC2/S3/RDS/Lambda cost calculator on live AWS On-Demand prices + CSV export. |

## PI-change test loop (the point of this repo)
1. Change PIs: edit `PIS` in `seed/generate_sample_issues.py` and/or `PI_WINDOWS` in
`.github/scripts/generate_fte_report.py`; or add a PI option on the board + in the workflow's
`pi` dropdown.
2. Re-seed (or edit the board directly) and re-run the Action.
3. Diff the report on `fte-report/<pi>` to see the effect.

## Reset / reseed
```bash
python seed/cleanup_issues.py --delete # remove the demo issues (dry-run without a flag)
```
Delete/recreate the board in the GitHub UI for a fully clean slate.
## Repo layout
| Path | Role |
|---|---|
| [`action.yml`](action.yml), [`pr-finder/`](pr-finder/), [`leave/`](leave/) | the three reusable composite actions |
| [`.github/scripts/`](.github/scripts/), [`aws-pricing/`](aws-pricing/), [`seed/`](seed/) | generators + seed/bootstrap tooling (stdlib Python) |
| [`.github/workflows/`](.github/workflows/) | self-consumer workflows that run each action/generator and publish to a report branch |
| `*-dashboard/`, [`dse-hub/`](dse-hub/) | the Netlify SPAs above |
| [`docs/`](docs/) | per-module deep-dives |

## Dashboard + Netlify
Static Vite + React SPA. Its build config lives in **`fte-dashboard/netlify.toml`** (base-relative:
build `npm run build`, publish `dist`). Connect a Netlify site to this repo on `main` with
**base directory = `fte-dashboard`**; Netlify reads that folder's `netlify.toml`. Because the repo is
public, the deployed SPA fetches `fte-report/all-pis` at runtime, so new reports appear on reload with
**no redeploy**. Local dev:
```bash
cd fte-dashboard && npm ci && npm run dev
```
## Per-module docs
- **FTE Capacity Report** — seed, PI-change test loop, reset/reseed, board fields: [`docs/FTE_SEED.md`](docs/FTE_SEED.md)
- **PR Finder** — crawl model, seed harness, gotchas: [`docs/PR_FINDER.md`](docs/PR_FINDER.md)
- **Leave Tracker** — color map, overrides, gotchas: [`docs/LEAVE_TRACKER.md`](docs/LEAVE_TRACKER.md)
- **DSE Hub** — tabs, recurrence, timezone, Changes cart: [`docs/DSE_HUB.md`](docs/DSE_HUB.md)
- **AWS pricing generator** — Price List API pull, weekly review: [`docs/AWS_PRICING.md`](docs/AWS_PRICING.md)
- **Design decisions log**: [`docs/DECISIONS.md`](docs/DECISIONS.md)

**One independent site per dashboard.** This repo is a test station for many Actions, so each
dashboard is its **own** Netlify site: give it its own `<app>/netlify.toml` and connect a new site
with **base directory = that folder**. Keep **no `netlify.toml` at the repo root** — Netlify applies
a root config (and its `base` key) to *every* site connected to the repo, which would hijack the
others into building the wrong app.
## Conventions (non-obvious)
- Generators are **stdlib-only Python 3.12 + `gh` subprocess** — no `requests`, no pip deps.
- **"Objective"** = the issue title contains `"objective"` (case-insensitive). Shared by the actions.
- **One Netlify site per dashboard; no root `netlify.toml`** — a root config (and its `base` key)
applies to *every* site connected to the repo and would hijack the others into building the wrong app.
- Root `.gitignore` has `*.json`, so `git add <dir>` silently skips needed JSON (each dashboard's
`package.json`/`tsconfig*.json`, data JSON) — you must **`git add -f`** them.

---
Ported from `Disasters-Learning-Portal/disasters-aws-conversion` (PR #74). See `docs/FTE_SEED.md`.
Ported from `Disasters-Learning-Portal/disasters-aws-conversion` (PR #74). See [`docs/FTE_SEED.md`](docs/FTE_SEED.md).