diff --git a/.github/workflows/update-reports.yml b/.github/workflows/update-reports.yml index 3246db4..93ea1c0 100644 --- a/.github/workflows/update-reports.yml +++ b/.github/workflows/update-reports.yml @@ -4,68 +4,15 @@ on: schedule: # Run every Monday at 9 AM ET (14:00 UTC) - cron: '0 14 * * 1' - push: - branches: - - main - paths: - - 'reports/*.py' - - 'reports/pyproject.toml' workflow_dispatch: jobs: - update-reports: - runs-on: ubuntu-latest + reports: + uses: NASA-IMPACT/dse-oss-reports/.github/workflows/reports.yml@33eb4f01af27504d3a35e32fbbc679435e300b5b # v0.2.0 permissions: contents: write pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Get current date - id: date - run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - version: "0.9.*" - enable-cache: true - - - name: Generate config data - working-directory: reports - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_PAT: ${{ secrets.GH_PAT }} - run: uv run generate_config.py - - - name: Generate commit data - working-directory: reports - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_PAT: ${{ secrets.GH_PAT }} - run: uv run main.py - - - name: Generate plot - working-directory: reports - run: uv run plot.py - - - name: Generate docs page - working-directory: reports - run: uv run generate_docs.py - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.11 - with: - commit-message: "Update reports for ${{ github.run_id }}" - title: "Update reports (${{ steps.date.outputs.date }})" - body: | - Automated update of commit reports and visualization. - - Generated by GitHub Actions workflow. - branch: update-reports - add-paths: | - reports/output/ - docs/images/ - docs/objectives.md + with: + dse-oss-reports-ref: further-simplification + secrets: + pat: ${{ secrets.GH_PAT }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..511fea4 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# science-support + +Documentation site for the VEDA/EODC Science Support team. Published at . + +## Reporting pipeline + +Quarterly objectives, commit charts, and the [Objectives page](https://nasa-impact.github.io/science-support/objectives) are auto-generated by [`dse-oss-reports`](https://github.com/NASA-IMPACT/dse-oss-reports). All team-specific configuration lives in [`team.toml`](./team.toml). Adoption guide and architecture: . + +The weekly cron in [`.github/workflows/update-reports.yml`](./.github/workflows/update-reports.yml) calls the library's reusable workflow, which scrapes GitHub issues, fetches commit data, regenerates charts and `docs/objectives.md`, and opens a PR for review. + +## Running the pipeline locally + +`dse-oss-reports` is pinned in `pyproject.toml`; `uv sync` installs it alongside mkdocs. Then prefix every command with `uv run`. + +All commands take `--config team.toml` and run from the repo root. + +| Subcommand | What it does | Reads | Writes | PAT? | +|---|---|---|---|---| +| `current-pi` | Print the resolved current PI | `team.toml` | stdout | no | +| `generate-config` | Scrape GitHub issues into the objectives data file | GitHub API | `reports/_objectives_data.json` | yes | +| `fetch` | Pull authored commits + resolved items for a PI | GitHub API, `_objectives_data.json` | `reports/output/{pi}-*.csv` | yes | +| `plot` | Render per-PI charts | `reports/output/{pi}-*.csv` | `docs/images/{pi}-*.png` | no | +| `generate-docs` | Render the objectives page | `_objectives_data.json`, existing PNGs | `docs/objectives.md` | no | +| `run-all` | Run all four pipeline stages in order | everything | everything | yes | + +PAT-requiring commands read `DSE_OSS_REPORTS_TOKEN` → `GH_PAT` → `GITHUB_TOKEN` (in that order). Create a fine-grained PAT with public-repo read access at . + +### Common workflows + +```bash +# Full refresh from GitHub (PAT required, ~1-2 minutes) +export GH_PAT=ghp_... +uv run dse-oss-reports --config team.toml run-all + +# Regenerate figures from existing CSVs (no PAT, ~5 seconds) +uv run dse-oss-reports --config team.toml plot # current PI +uv run dse-oss-reports --config team.toml plot --pi pi-26.2 # a specific past PI + +# Regenerate the docs page after charts change (no PAT) +uv run dse-oss-reports --config team.toml generate-docs +``` + +Once `dse-oss-reports` is released, bump the pin in `pyproject.toml` and both refs in `.github/workflows/update-reports.yml` from `@further-simplification` to a versioned tag. + +## Building the docs site + +```bash +uv sync +uv run mkdocs serve # preview at http://127.0.0.1:8000 +``` diff --git a/docs/images/pi-26.3-authored-commits.png b/docs/images/pi-26.3-authored-commits.png new file mode 100644 index 0000000..264e375 Binary files /dev/null and b/docs/images/pi-26.3-authored-commits.png differ diff --git a/docs/images/pi-26.3-resolved-issues-prs.png b/docs/images/pi-26.3-resolved-issues-prs.png new file mode 100644 index 0000000..887cf5b Binary files /dev/null and b/docs/images/pi-26.3-resolved-issues-prs.png differ diff --git a/docs/objectives.md b/docs/objectives.md index ed0ea9e..b2929f5 100644 --- a/docs/objectives.md +++ b/docs/objectives.md @@ -1,40 +1,52 @@ # Quarterly Objectives -This page tracks quarterly objectives and their related repositories across Program Increments (PIs). +This page tracks quarterly objectives for the VEDA/MAAP Science Support team and the open-source repositories they touch across Program Increments (PIs). -## Current PI: 26.2 +## Current PI: 26.3 + +![PI-26.3 authored commits](images/pi-26.3-authored-commits.png) + +![PI-26.3 resolved issues and PRs](images/pi-26.3-resolved-issues-prs.png) | # | Objective | Contributors | Repos | |---|-----------|--------------|-------| -| [#1](https://github.com/NASA-IMPACT/science-support/issues/1) | Hub Support | wildintellect, jsignell | repo2docker-action, pangeo-docker-images, pangeo-notebook-veda-image | -| [#2](https://github.com/NASA-IMPACT/science-support/issues/2) | Cloud Optimized Workflows | wildintellect, jsignell | veda-docs, maap-documentation, cloud-optimized-geospatial-formats-guide | -| [#3](https://github.com/NASA-IMPACT/science-support/issues/3) | Open-Source Contributions | jsignell, ircwaves, tylanderson | stac-best-practices, stac-spec, dask, pystac, pystac-client, xarray | -| [#9](https://github.com/NASA-IMPACT/science-support/issues/9) | Data Retention Policy | smk0033 | - | -| [#10](https://github.com/NASA-IMPACT/science-support/issues/10) | VEDA Forum (Stretch) | smk0033 | - | -| [#11](https://github.com/NASA-IMPACT/science-support/issues/11) | AI Embedding Report (Stretch) | omshinde | - | -| [#12](https://github.com/NASA-IMPACT/science-support/issues/12) | Merge MAAP Documentation into VEDA (Stretch) | | - | +| [#31](https://github.com/NASA-IMPACT/science-support/issues/31) | Hub Upgrades | wildintellect, grallewellyn | repo2docker-action, pangeo-docker-images, pangeo-notebook-veda-image | +| [#32](https://github.com/NASA-IMPACT/science-support/issues/32) | Cloud Optimized Workflows | wildintellect, tylanderson | veda-docs, maap-documentation, cloud-optimized-geospatial-formats-guide | +| [#33](https://github.com/NASA-IMPACT/science-support/issues/33) | Open-Source Contributions | gadomski, tylanderson | stac-best-practices, stac-spec, dask, pystac, pystac-client, xarray | +| [#34](https://github.com/NASA-IMPACT/science-support/issues/34) | Data Retention Policy | smk0033 | - | --- ---- +## Past PIs + +
+PI 26.2 (7 original objectives; 4 closed as completed; 3 closed as not planned) -## Visualization +| # | Objective | State | Contributors | +|---|-----------|-------|--------------| +| [#1](https://github.com/NASA-IMPACT/science-support/issues/1) | Hub Support | closed (completed) | wildintellect, jsignell | +| [#2](https://github.com/NASA-IMPACT/science-support/issues/2) | Cloud Optimized Workflows | closed (completed) | wildintellect, jsignell | +| [#3](https://github.com/NASA-IMPACT/science-support/issues/3) | Open-Source Contributions | closed (completed) | jsignell, ircwaves, tylanderson | +| [#9](https://github.com/NASA-IMPACT/science-support/issues/9) | Data Retention Policy | closed (completed) | smk0033 | +| [#10](https://github.com/NASA-IMPACT/science-support/issues/10) | VEDA Forum (Stretch) | closed (not planned) | smk0033 | +| [#11](https://github.com/NASA-IMPACT/science-support/issues/11) | AI Embedding Report (Stretch) | closed (not planned) | omshinde | +| [#12](https://github.com/NASA-IMPACT/science-support/issues/12) | Merge MAAP Documentation into VEDA (Stretch) | closed (not planned) | - | -The charts use color-coding to show which objective each repo contributes to. Repos that contribute to multiple objectives are shown with split bars. +![PI-26.2 authored commits](images/pi-26.2-authored-commits.png) -![PI-26.2 Commits per Repository](images/pi-26.2-authored-commits.png) +![PI-26.2 resolved issues and PRs](images/pi-26.2-resolved-issues-prs.png) -![PI-26.2 Resolved issues/PRs](images/pi-26.2-resolved-issues-prs.png) +
--- ## Configuration -Objectives are configured in [`reports/config.py`](https://github.com/NASA-IMPACT/science-support/blob/main/reports/config.py). +Objectives data lives in [`reports/_objectives_data.py`](https://github.com/NASA-IMPACT/science-support/blob/main/reports/_objectives_data.py) — auto-generated from GitHub issues by `dse_oss_reports.generator.ObjectivesGenerator`. -To regenerate this page from config: +To regenerate this page: ```bash cd reports uv run generate_docs.py -``` \ No newline at end of file +``` diff --git a/pyproject.toml b/pyproject.toml index f1dba72..a254625 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,11 @@ version = "0.1.0" description = "Science Support for NASA VEDA and EODC" readme = "README.md" dependencies = [] -requires-python = ">= 3.11" +requires-python = ">= 3.12" [dependency-groups] dev = [ + "dse-oss-reports @ git+https://github.com/NASA-IMPACT/dse-oss-reports.git@33eb4f01af27504d3a35e32fbbc679435e300b5b", #v0.2.0 "mike>=2.1.3", "mkdocs-material[imaging]>=9.6.3", "mkdocs-jupyter>=0.25.0", diff --git a/reports/README.md b/reports/README.md deleted file mode 100644 index 15394de..0000000 --- a/reports/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Generating open source commit statistics for the VEDA ODD team - -## Setting up a fine-grained personal access token - -1. Navigate to https://github.com/settings/personal-access-tokens/new -2. Select public repositories -3. Add new token as the environment variable specified by `TOKEN_ENV_VAR` in `settings.py` (default: `GH_PAT`) - -## Configuration - -The `config.py` file contains: -- `TIME_RANGE`: Start and end dates for commit analysis -- `OBJECTIVES`: Quarterly objectives with repos and contributors per objective - -### Regenerating objectives from GitHub - -To fetch the latest objectives from GitHub issues: - -```bash -uv run generate_config.py -``` - -This generates `objectives_config.py` with objectives and contributors from issues labeled `pi-*-objective`. You'll need to manually add repos to each objective, then copy to `config.py`. - -## Generating data - -1. Run `uv run main.py` (uses 10 parallel workers by default) -2. Run `uv run plot.py` - -`TIME_RANGE` is automatically set to the current fiscal quarter (Q1: Oct-Dec, Q2: Jan-Mar, Q3: Apr-Jun, Q4: Jul-Sep). - -The generated chart colors bars by PI objective (see the objectives page on the deployed site for details). - -### Regenerating docs/objectives.md - -To regenerate the objectives documentation page from config: - -```bash -uv run generate_docs.py -``` - -## Performance - -- **generate_config.py**: Uses GitHub search API to fetch only objective issues (~2-3 seconds) -- **main.py**: Parallelizes API calls with ThreadPoolExecutor (10x faster than sequential) diff --git a/reports/_objectives_data.json b/reports/_objectives_data.json new file mode 100644 index 0000000..8007af0 --- /dev/null +++ b/reports/_objectives_data.json @@ -0,0 +1,274 @@ +{ + "pi-26.2": [ + { + "issue_number": 1, + "title": "[Sc] PI 26.2 Objective 1: Hub Support", + "state": "closed", + "state_reason": "completed", + "contributors": [ + [ + "Alex I. Mandel", + "wildintellect" + ], + [ + "Julia Signell", + "jsignell" + ] + ], + "repos": [ + [ + "jupyterhub", + "repo2docker-action" + ], + [ + "pangeo-data", + "pangeo-docker-images" + ], + [ + "NASA-IMPACT", + "pangeo-notebook-veda-image" + ] + ] + }, + { + "issue_number": 2, + "title": "[Sc] PI 26.2 Objective 2: Cloud Optimized Workflows", + "state": "closed", + "state_reason": "completed", + "contributors": [ + [ + "Alex I. Mandel", + "wildintellect" + ], + [ + "Julia Signell", + "jsignell" + ] + ], + "repos": [ + [ + "NASA-IMPACT", + "veda-docs" + ], + [ + "MAAP-Project", + "maap-documentation" + ], + [ + "cloudnativegeo", + "cloud-optimized-geospatial-formats-guide" + ] + ] + }, + { + "issue_number": 3, + "title": "[Sc] PI 26.2 Objective 3: Open-Source Contributions", + "state": "closed", + "state_reason": "completed", + "contributors": [ + [ + "Julia Signell", + "jsignell" + ], + [ + "Ian Cooke", + "ircwaves" + ], + [ + "Tyler", + "tylanderson" + ] + ], + "repos": [ + [ + "radiantearth", + "stac-best-practices" + ], + [ + "radiantearth", + "stac-spec" + ], + [ + "dask", + "dask" + ], + [ + "stac-utils", + "pystac" + ], + [ + "stac-utils", + "pystac-client" + ], + [ + "pydata", + "xarray" + ] + ] + }, + { + "issue_number": 9, + "title": "[Sc] PI 26.2 Objective 4: Data Retention Policy", + "state": "closed", + "state_reason": "completed", + "contributors": [ + [ + "Sheyenne Kirkland", + "smk0033" + ] + ], + "repos": [] + }, + { + "issue_number": 10, + "title": "[Sc] PI 26.2 Objective 5: VEDA Forum (Stretch)", + "state": "closed", + "state_reason": "not_planned", + "contributors": [ + [ + "Sheyenne Kirkland", + "smk0033" + ] + ], + "repos": [] + }, + { + "issue_number": 11, + "title": "[Sc] PI 26.2 Objective 6: AI Embedding Report (Stretch)", + "state": "closed", + "state_reason": "not_planned", + "contributors": [ + [ + "Rajat Shinde", + "omshinde" + ] + ], + "repos": [] + }, + { + "issue_number": 12, + "title": "[Sc] PI 26.2 Objective 7: Merge MAAP Documentation into VEDA (Stretch)", + "state": "closed", + "state_reason": "not_planned", + "contributors": [], + "repos": [] + } + ], + "pi-26.3": [ + { + "issue_number": 31, + "title": "[Sc] PI 26.3 Objective 1: Hub Upgrades", + "state": "open", + "state_reason": null, + "contributors": [ + [ + "Alex I. Mandel", + "wildintellect" + ], + [ + "Grace Llewellyn", + "grallewellyn" + ] + ], + "repos": [ + [ + "jupyterhub", + "repo2docker-action" + ], + [ + "pangeo-data", + "pangeo-docker-images" + ], + [ + "NASA-IMPACT", + "pangeo-notebook-veda-image" + ] + ] + }, + { + "issue_number": 32, + "title": "[Sc] PI 26.3 Objective 2: Cloud Optimized Workflows", + "state": "open", + "state_reason": "reopened", + "contributors": [ + [ + "Alex I. Mandel", + "wildintellect" + ], + [ + "Tyler", + "tylanderson" + ] + ], + "repos": [ + [ + "NASA-IMPACT", + "veda-docs" + ], + [ + "MAAP-Project", + "maap-documentation" + ], + [ + "cloudnativegeo", + "cloud-optimized-geospatial-formats-guide" + ] + ] + }, + { + "issue_number": 33, + "title": "[Sc] PI 26.3 Objective 3: Open-Source Contributions", + "state": "open", + "state_reason": null, + "contributors": [ + [ + "Pete Gadomski", + "gadomski" + ], + [ + "Tyler", + "tylanderson" + ] + ], + "repos": [ + [ + "radiantearth", + "stac-best-practices" + ], + [ + "radiantearth", + "stac-spec" + ], + [ + "dask", + "dask" + ], + [ + "stac-utils", + "pystac" + ], + [ + "stac-utils", + "pystac-client" + ], + [ + "pydata", + "xarray" + ] + ] + }, + { + "issue_number": 34, + "title": "[Sc] PI 26.3 Objective 4: Data Retention Policy", + "state": "open", + "state_reason": null, + "contributors": [ + [ + "Sheyenne Kirkland", + "smk0033" + ] + ], + "repos": [] + } + ] +} diff --git a/reports/generate_config.py b/reports/generate_config.py deleted file mode 100644 index 06bcd14..0000000 --- a/reports/generate_config.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate OBJECTIVES config from GitHub issues with pi-*-objective labels. - -Data sources: -- Objectives: Issues with `pi-X.Y-objective` labels -- Contributors: Issue assignees -- Repos: Labels matching `repo:org/repo-name` pattern - -Usage: - uv run generate_config.py -""" - -import os -import re -from github import Github, Auth -from settings import REPO_FULL_NAME, TOKEN_ENV_VAR - - -# Labels have length limits, to get around this use an abbreviation -# and add it to this mapping -LONG_ORG_NAME_MAPPING = { - "cng": "cloudnativegeo" -} - - -def get_objective_issues(g: Github, repo_name: str = REPO_FULL_NAME): - """Fetch all issues with pi-*-objective labels using search API.""" - objectives_by_pi = {} - - # Use search API - much faster than iterating all issues - # Search for issues with any pi-*-objective label - query = f"repo:{repo_name} is:issue label:pi-25.2-objective,pi-25.3-objective,pi-25.4-objective,pi-26.1-objective,pi-26.2-objective,pi-26.3-objective,pi-26.4-objective" - issues = g.search_issues(query) - - if issues.totalCount < 1: - raise (ValueError, "No PI issue found") - for issue in issues: - pi = None - repos = [] - - for label in issue.labels: - # Check for PI objective label - match = re.match(r"pi-(\d+\.\d+)-objective", label.name) - if match: - pi = f"pi-{match.group(1)}" - - # Check for repo label (format: repo:org/repo-name) - if label.name.startswith("repo:"): - repo_str = label.name[5:] # Remove "repo:" prefix - if "/" in repo_str: - org, repo_name_part = repo_str.split("/", 1) - # Replace org abbreviations with full names - if org in LONG_ORG_NAME_MAPPING: - org = LONG_ORG_NAME_MAPPING[org] - repos.append((org, repo_name_part)) - - if pi: - if pi not in objectives_by_pi: - objectives_by_pi[pi] = [] - - # Get assignees - contributors = [ - (assignee.name or assignee.login, assignee.login) - for assignee in issue.assignees - ] - - objectives_by_pi[pi].append( - { - "issue_number": issue.number, - "title": issue.title, - "contributors": contributors, - "state": issue.state, - "repos": repos, - } - ) - - return objectives_by_pi - - -def generate_config(objectives_by_pi: dict) -> str: - """Generate Python config code from objectives data.""" - lines = [ - "from datetime import date", - "", - "# Manually maintained PI date ranges", - "# Update these when new PIs are planned", - "PI_DATES = {", - ' "pi-25.2": ("20250119", "20250418"),', - ' "pi-25.3": ("20250419", "20250718"),', - ' "pi-25.4": ("20250719", "20251018"),', - ' "pi-26.1": ("20251019", "20260117"),', - ' "pi-26.2": ("20260118", "20260425"),', - ' "pi-26.3": ("20260426", "20260711"),', - ' "pi-26.4": ("20260712", "20261017"),', - "}", - "", - "", - "def get_current_pi():", - ' """Find the current PI based on today\'s date."""', - ' today = date.today().strftime("%Y%m%d")', - " for pi_name, (start, end) in PI_DATES.items():", - " if start <= today <= end:", - " return pi_name", - " return None", - "", - "", - "def get_time_range(pi: str = None):", - ' """Get date range for a PI, or current PI if not specified."""', - " if pi:", - " return PI_DATES.get(pi)", - " current = get_current_pi()", - " if current:", - " return PI_DATES[current]", - " # Fallback to most recent PI if not in any range", - " return list(PI_DATES.values())[-1]", - "", - "", - "TIME_RANGE = get_time_range()", - "", - "# Quarterly objectives with repos and contributors per objective", - "# Run `uv run generate_config.py` to regenerate from GitHub issues", - "# - Objectives: Issues with pi-X.Y-objective labels", - "# - Contributors: Issue assignees", - "# - Repos: Labels matching repo:org/repo-name", - "OBJECTIVES = {", - ] - - # Sort PIs chronologically - sorted_pis = sorted(objectives_by_pi.keys(), key=lambda x: float(x.split("-")[1])) - - for pi in sorted_pis: - objectives = objectives_by_pi[pi] - lines.append(f' "{pi}": [') - - # Sort objectives by issue number - for obj in sorted(objectives, key=lambda x: x["issue_number"]): - lines.append(" {") - lines.append(f' "issue_number": {obj["issue_number"]},') - title = obj["title"].replace('"', '\\"') - lines.append(f' "title": "{title}",') - lines.append(f' "state": "{obj["state"]}",') - lines.append(' "contributors": [') - for name, username in obj["contributors"]: - name = (name or username).replace('"', '\\"') - lines.append(f' ("{name}", "{username}"),') - lines.append(" ],") - lines.append(' "repos": [') - for org, repo in obj.get("repos", []): - lines.append(f' ("{org}", "{repo}"),') - lines.append(" ],") - lines.append(" },") - - lines.append(" ],") - - lines.append("}") - lines.append("") - lines.append("") - lines.append("def get_all_repos():") - lines.append(' """Derive unique repos from all objectives."""') - lines.append(" repos = set()") - lines.append(" for pi_objectives in OBJECTIVES.values():") - lines.append(" for obj in pi_objectives:") - lines.append(' for repo in obj["repos"]:') - lines.append(" repos.add(repo)") - lines.append(" return sorted(repos)") - lines.append("") - lines.append("") - lines.append("def get_all_contributors():") - lines.append(' """Derive unique contributors from all objectives."""') - lines.append(" contributors = {}") - lines.append(" for pi_objectives in OBJECTIVES.values():") - lines.append(" for obj in pi_objectives:") - lines.append(' for name, username in obj["contributors"]:') - lines.append(" contributors[username] = name") - lines.append( - " return [(name, username) for username, name in sorted(contributors.items(), key=lambda x: x[1])]" - ) - lines.append("") - lines.append("") - lines.append("def get_repos_for_pi(pi: str):") - lines.append(' """Get all repos for a specific PI."""') - lines.append(" repos = set()") - lines.append(" for obj in OBJECTIVES.get(pi, []):") - lines.append(' for repo in obj["repos"]:') - lines.append(" repos.add(repo)") - lines.append(" return sorted(repos)") - lines.append("") - lines.append("") - lines.append("def get_repos_x_contributors_for_pi(pi: str):") - lines.append(' """Get all repos for a specific PI."""') - lines.append(" repos = set()") - lines.append(" for obj in OBJECTIVES.get(pi, []):") - lines.append(' for repo in obj["repos"]:') - lines.append(' for _, username in obj["contributors"]:') - lines.append(" repos.add(tuple([*repo, username]))") - lines.append(" return sorted(repos)") - lines.append("") - lines.append("") - lines.append("def get_contributors_for_pi(pi: str):") - lines.append(' """Get all contributors for a specific PI."""') - lines.append(" contributors = {}") - lines.append(" for obj in OBJECTIVES.get(pi, []):") - lines.append(' for name, username in obj["contributors"]:') - lines.append(" contributors[username] = name") - lines.append( - " return [(name, username) for username, name in sorted(contributors.items(), key=lambda x: x[1])]" - ) - - return "\n".join(lines) - - -def main(): - token = os.environ.get(TOKEN_ENV_VAR) or os.environ.get("GITHUB_TOKEN") - if not token: - raise ValueError(f"Set {TOKEN_ENV_VAR} or GITHUB_TOKEN environment variable") - - auth = Auth.Token(token) - g = Github(auth=auth) - - print("Fetching objective issues from GitHub (using search API)...") - objectives_by_pi = get_objective_issues(g) - - g.close() - - print(f"Found {len(objectives_by_pi)} PIs:") - for pi, objs in sorted(objectives_by_pi.items()): - repos_count = sum(len(o["repos"]) for o in objs) - print(f" {pi}: {len(objs)} objectives, {repos_count} repo mappings") - - config_code = generate_config(objectives_by_pi) - - output_file = "config.py" - with open(output_file, "w") as f: - f.write(config_code) - print(f"\nGenerated config written to {output_file}") - print("\nTo add repos to an objective, add labels like:") - print(" repo:zarr-developers/VirtualiZarr") - print(" repo:developmentseed/titiler-cmr") - - -if __name__ == "__main__": - main() diff --git a/reports/generate_docs.py b/reports/generate_docs.py deleted file mode 100644 index 2d42f25..0000000 --- a/reports/generate_docs.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -""" -Generate docs/objectives.md from config.py OBJECTIVES. - -Usage: - uv run generate_docs.py -""" - -from config import OBJECTIVES -from settings import REPO_URL - - -def generate_objectives_md() -> str: - """Generate markdown content for objectives page.""" - lines = [ - "# Quarterly Objectives", - "", - "This page tracks quarterly objectives and their related repositories across Program Increments (PIs).", - "", - ] - - # Sort PIs reverse chronologically (newest first) - sorted_pis = sorted( - OBJECTIVES.keys(), key=lambda x: float(x.split("-")[1]), reverse=True - ) - - for i, pi in enumerate(sorted_pis): - objectives = OBJECTIVES[pi] - pi_upper = pi.upper().replace("-", " ") - - if i == 0: - # Current PI - show full details - lines.append(f"## Current PI: {pi.split('-')[1]}") - lines.append("") - lines.append("| # | Objective | Contributors | Repos |") - lines.append("|---|-----------|--------------|-------|") - - for obj in sorted(objectives, key=lambda x: x["issue_number"]): - num = obj["issue_number"] - # Clean up title (remove PI prefix if present) - title = obj["title"] - if "Objective" in title and ":" in title: - title = title.split(":", 1)[1].strip() - title = title[:60] + "..." if len(title) > 60 else title - - contributors = ", ".join(u for _, u in obj["contributors"]) - repos = ", ".join(r for _, r in obj["repos"]) if obj["repos"] else "-" - - lines.append( - f"| [#{num}]({REPO_URL}/issues/{num}) | {title} | {contributors} | {repos} |" - ) - - lines.append("") - lines.append("---") - lines.append("") - else: - # Historical PIs - collapsible - closed_count = sum(1 for o in objectives if o["state"] == "closed") - - lines.append("
") - lines.append( - f"{pi_upper} ({len(objectives)} objectives, {closed_count} closed)" - ) - lines.append("") - lines.append("| # | Objective | State | Contributors |") - lines.append("|---|-----------|-------|--------------|") - - for obj in sorted(objectives, key=lambda x: x["issue_number"]): - num = obj["issue_number"] - title = obj["title"] - if "Objective" in title and ":" in title: - title = title.split(":", 1)[1].strip() - title = title[:50] + "..." if len(title) > 50 else title - - state = obj["state"] - contributors = ", ".join(u for _, u in obj["contributors"]) - - lines.append( - f"| [#{num}]({REPO_URL}/issues/{num}) | {title} | {state} | {contributors} |" - ) - - lines.append("") - lines.append("
") - lines.append("") - - lines.append("---") - lines.append("") - lines.append("## Visualization") - lines.append("") - lines.append( - "The charts use color-coding to show which objective each repo contributes to. Repos that contribute to multiple objectives are shown with split bars." - ) - lines.append("") - # Add image for the current PI - current_pi = sorted_pis[0] - lines.append( - f"![{current_pi.upper()} Commits per Repository](images/{current_pi}-authored-commits.png)" - ) - lines.append("") - lines.append( - f"![{current_pi.upper()} Resolved issues/PRs](images/{current_pi}-resolved-issues-prs.png)" - ) - lines.append("") - lines.append("---") - lines.append("") - lines.append("## Configuration") - lines.append("") - lines.append( - f"Objectives are configured in [`reports/config.py`]({REPO_URL}/blob/main/reports/config.py)." - ) - lines.append("") - lines.append("To regenerate this page from config:") - lines.append("") - lines.append("```bash") - lines.append("cd reports") - lines.append("uv run generate_docs.py") - lines.append("```") - - return "\n".join(lines) - - -def main(): - content = generate_objectives_md() - - output_file = "../docs/objectives.md" - with open(output_file, "w") as f: - f.write(content) - - print(f"Generated {output_file}") - - # Print summary - total_objectives = sum(len(objs) for objs in OBJECTIVES.values()) - print(f" {len(OBJECTIVES)} PIs, {total_objectives} total objectives") - - -if __name__ == "__main__": - main() diff --git a/reports/main.py b/reports/main.py deleted file mode 100644 index 7f6ca81..0000000 --- a/reports/main.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -""" -Query GitHub API for commits to repositories in parallel. -""" - -from github import Github, Auth -from datetime import datetime -from typing import List -from concurrent.futures import ThreadPoolExecutor, as_completed -import os -import pandas as pd -from config import ( - get_time_range, - get_current_pi, - get_contributors_for_pi, - get_repos_x_contributors_for_pi, -) -from settings import TOKEN_ENV_VAR - - -def get_commits_for_repo_author( - g: Github, - owner: str, - repo: str, - author: str, - start_date: datetime, - end_date: datetime, -) -> List[dict]: - """ - Query GitHub API for commits by a specific author in a repo. - - Returns list of commit detail dicts (not commit objects) to avoid - thread safety issues with PyGithub objects. - """ - try: - repository = g.get_repo(f"{owner}/{repo}") - commits = repository.get_commits( - author=author, since=start_date, until=end_date - ) - - # Group commits by PR - prs = [] - pr_commits = [] - standalone_commits = [] - - for commit in commits: - pulls = commit.get_pulls() - if pulls.totalCount == 1: - if (number := pulls[0].number) not in prs: - pr_commits.append(commit) - prs.append(number) - elif pulls.totalCount == 0: - standalone_commits.append(commit) - - # Extract details immediately (avoid returning PyGithub objects) - results = [] - for commit in pr_commits + standalone_commits: - results.append( - { - "sha": commit.sha, - "message": commit.commit.message.split("\n")[0], - "author": commit.commit.author.name, - "committer": commit.commit.committer.name, - "url": commit.html_url, - "total_changes": commit.stats.total if commit.stats else 0, - "organization": owner, - "repository": repo, - } - ) - return results - except Exception as e: - print(f" Error processing {owner}/{repo} for {author}: {e}") - return [] - - -def get_resolved_for_contributor( - g: Github, - tasks: List[tuple], - contributor: str, - start_date: datetime, - end_date: datetime, -) -> List[dict]: - """ - Query GitHub API for closed issues and PRs that a contributor was involved with across repos. - - "Involved" means the contributor was the author, assignee, mentioned, or commented. - Uses the GitHub search API with the `involves:` qualifier and multiple `repo:` filters - in a single query, then returns only results matching the configured repos. - - Returns list of issue/PR detail dicts (not PyGithub objects) to avoid - thread safety issues. - """ - try: - start_str = start_date.strftime("%Y-%m-%d") - end_str = end_date.strftime("%Y-%m-%d") - repo_filters = " ".join(f"repo:{owner}/{repo}" for owner, repo, _ in tasks) - base_query = ( - f"{repo_filters} " - f"involves:{contributor} " - f"closed:{start_str}..{end_str}" - ) - - issues = g.search_issues(f"is:issue {base_query}") - prs = g.search_issues(f"is:pr {base_query} -author:{contributor}") - - results = [] - for item in [*issues, *prs]: - # item.repository is a Repository object with owner.login and name - item_owner = item.repository.owner.login - item_repo = item.repository.name - if (item_owner, item_repo, contributor) not in tasks: - continue - is_pr = item.pull_request is not None - results.append( - { - "number": item.number, - "title": item.title, - "type": "PR" if is_pr else "Issue", - "state": item.state, - "author": item.user.login if item.user else None, - "url": item.html_url, - "created_at": item.created_at.isoformat() if item.created_at else None, - "updated_at": item.updated_at.isoformat() if item.updated_at else None, - "organization": item_owner, - "repository": item_repo, - "contributor": contributor, - } - ) - return results - except Exception as e: - print(f" Error fetching issues/PRs for {contributor}: {e}") - return [] - - -def main(token: str = None, pi: str = None, max_workers: int = 10): - """ - Query GitHub for commits using parallel requests. - - Args: - token: GitHub personal access token - pi: Optional PI to filter repos/contributors (e.g., "pi-26.1"). - If None, uses current PI based on today's date. - max_workers: Number of parallel threads (default 10) - """ - # Default to current PI if not specified - if pi is None: - pi = get_current_pi() - - time_range = get_time_range(pi) - if not time_range: - raise ValueError(f"No date range found for PI: {pi}") - - time_start = datetime.strptime(time_range[0], "%Y%m%d") - time_end = datetime.strptime(time_range[1], "%Y%m%d") - - # Get repos tasks for the PI - contributors = get_contributors_for_pi(pi) - tasks = get_repos_x_contributors_for_pi(pi) - print( - f"PI: {pi} ({time_start.strftime('%Y-%m-%d')} to {time_end.strftime('%Y-%m-%d')})" - ) - print(f" {len(tasks)} repos x contributors") - - if len(tasks) < 1: - raise ValueError("No repos x contributors found in config.") - - print( - f"Querying {len(tasks)} repo×contributor combinations with {max_workers} workers..." - ) - - all_commits = [] - all_resolved = [] - - # Use thread pool for parallel API calls - # Each thread gets its own Github client to avoid rate limit issues - def make_client(): - if token: - return Github(auth=Auth.Token(token)) - return Github() - - def process_commits_task(task): - owner, repo, username = task - g = make_client() - try: - return get_commits_for_repo_author( - g, owner, repo, username, time_start, time_end - ) - finally: - g.close() - - def process_resolved_task(username): - g = make_client() - try: - return get_resolved_for_contributor( - g, tasks, username, time_start, time_end - ) - finally: - g.close() - - completed = 0 - with ThreadPoolExecutor(max_workers=max_workers) as executor: - commits_futures = {executor.submit(process_commits_task, task): task for task in tasks} - resolved_issues_futures = {executor.submit(process_resolved_task, username): username for _, username in contributors} - - for future in as_completed(commits_futures): - completed += 1 - print(f" Progress: {completed}/{len(tasks) + len(contributors)}") - commits = future.result() - all_commits.extend(commits) - - for future in as_completed(resolved_issues_futures): - completed += 1 - print(f" Progress: {completed}/{len(tasks)+ len(contributors)}") - resolved_issues = future.result() - all_resolved.extend(resolved_issues) - - print(f"Found {len(all_commits)} authored commits") - print(f"Found {len(all_resolved)} resolved issues/PRs") - - df = pd.DataFrame(all_commits) - csv_filename = f"output/{pi}-authored-commits.csv" - df.to_csv(csv_filename, index=False) - print(f"Saved to {csv_filename}") - - df_resolved = ( - pd.DataFrame(all_resolved) - .drop_duplicates(subset=["organization", "repository", "number"]) - .sort_values(by=["organization", "repository", "number"]) - ) - resolved_filename = f"output/{pi}-resolved-issues-prs.csv" - df_resolved.to_csv(resolved_filename, index=False) - print(f"Saved to {resolved_filename}") - - -if __name__ == "__main__": - token = os.environ.get(TOKEN_ENV_VAR) or os.environ.get("GITHUB_TOKEN") - main(token=token) diff --git a/reports/output/pi-26.3-authored-commits.csv b/reports/output/pi-26.3-authored-commits.csv new file mode 100644 index 0000000..1637d38 --- /dev/null +++ b/reports/output/pi-26.3-authored-commits.csv @@ -0,0 +1,8 @@ +sha,message,author,committer,url,total_changes,organization,repository +985b644aedd2930c1a72b3cb0f8130290b712020,Update authors and contributors in CITATION.cff (#197),Alex I. Mandel,GitHub,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/commit/985b644aedd2930c1a72b3cb0f8130290b712020,54,cloudnativegeo,cloud-optimized-geospatial-formats-guide +118a3bda2d5719289452c269973bf77038b083be,fix(ci): build into the subdirectory's `dist/` (#1729),Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/118a3bda2d5719289452c269973bf77038b083be,2,stac-utils,pystac +ecac0d0373c6c5d1beb73567c33b885a7bebd046,fix: remove python-version from release workflow (#1727),Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/ecac0d0373c6c5d1beb73567c33b885a7bebd046,2,stac-utils,pystac +44f948d1f6c99deee85b43b851257bcf402f8489,fix: update all of our versions to be rc.0 (#1726),Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/44f948d1f6c99deee85b43b851257bcf402f8489,81,stac-utils,pystac +81ff5a8bf62d85c9e7055fdf8f2ed422b92393be,chore: actually set release-please manifest correctly (#1723),Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/81ff5a8bf62d85c9e7055fdf8f2ed422b92393be,4,stac-utils,pystac +750f064c4fb89d995dd9400c771a5cb06587753f,chore: manually set all the release versions (#1721),Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/750f064c4fb89d995dd9400c771a5cb06587753f,48,stac-utils,pystac +697c021d76c00b34cdcb267cdd423f13b0795cb3,"fix(ci): use client-id, not app-id (#1720)",Pete Gadomski,GitHub,https://github.com/stac-utils/pystac/commit/697c021d76c00b34cdcb267cdd423f13b0795cb3,4,stac-utils,pystac diff --git a/reports/output/pi-26.3-resolved-issues-prs.csv b/reports/output/pi-26.3-resolved-issues-prs.csv new file mode 100644 index 0000000..4df1eac --- /dev/null +++ b/reports/output/pi-26.3-resolved-issues-prs.csv @@ -0,0 +1,23 @@ +number,title,type,state,author,url,created_at,updated_at,organization,repository,contributor +565,v5.1.0 release notes,PR,closed,grallewellyn,https://github.com/MAAP-Project/maap-documentation/pull/565,2026-03-03T23:49:53+00:00,2026-05-06T21:58:54+00:00,MAAP-Project,maap-documentation,wildintellect +577,Merge Hub Documentation,PR,closed,smk0033,https://github.com/MAAP-Project/maap-documentation/pull/577,2026-05-04T20:54:23+00:00,2026-05-08T16:24:19+00:00,MAAP-Project,maap-documentation,wildintellect +580,Docs Release,PR,closed,smk0033,https://github.com/MAAP-Project/maap-documentation/pull/580,2026-05-08T16:28:01+00:00,2026-05-13T15:56:44+00:00,MAAP-Project,maap-documentation,wildintellect +581,Merge Hub version of Docs to default,Issue,closed,wildintellect,https://github.com/MAAP-Project/maap-documentation/issues/581,2026-05-08T17:59:25+00:00,2026-05-13T16:02:34+00:00,MAAP-Project,maap-documentation,wildintellect +583,ADE Docs Notice,PR,closed,smk0033,https://github.com/MAAP-Project/maap-documentation/pull/583,2026-05-11T20:49:05+00:00,2026-05-12T15:57:09+00:00,MAAP-Project,maap-documentation,wildintellect +52,Clarify Copyright Assignment for Future License Upgrades,Issue,closed,zacdezgeo,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/issues/52,2023-09-06T22:39:52+00:00,2026-05-13T20:54:18+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +161,Standardize citation guidance,PR,closed,maxrjones,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/pull/161,2025-05-21T19:05:44+00:00,2026-05-13T20:54:19+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +173,Add Virtual Zarr Stores content,Issue,closed,jsignell,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/issues/173,2025-08-27T20:44:15+00:00,2026-04-30T20:01:32+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +188,Initial Data Producer Guide for VEDA (seeking feedback),PR,closed,siddharth0248,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/pull/188,2026-04-03T19:51:13+00:00,2026-05-13T20:14:35+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +189,Add new virtual zarr page and remove dedicated kerchunk page,PR,closed,jsignell,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/pull/189,2026-04-16T18:08:29+00:00,2026-04-30T20:01:36+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +196,Roadmap,PR,closed,abarciauskas-bgse,https://github.com/cloudnativegeo/cloud-optimized-geospatial-formats-guide/pull/196,2026-05-13T17:43:41+00:00,2026-05-15T00:00:51+00:00,cloudnativegeo,cloud-optimized-geospatial-formats-guide,wildintellect +1382,schemas.stacspec.org DNS configuration,Issue,closed,GermanHydrogen,https://github.com/radiantearth/stac-spec/issues/1382,2026-04-24T07:02:51+00:00,2026-05-04T14:26:22+00:00,radiantearth,stac-spec,gadomski +1653,"feat: implement earthquake, insar, order, processing and product STAC Extensions",PR,closed,sim13pods,https://github.com/stac-utils/pystac/pull/1653,2026-03-10T16:37:56+00:00,2026-05-07T01:31:04+00:00,stac-utils,pystac,gadomski +1708,feat(extensions): Deprecation of eo.Band and RasterBand in favor of common metadata band,PR,closed,s-boomi,https://github.com/stac-utils/pystac/pull/1708,2026-04-23T13:34:41+00:00,2026-05-07T01:32:43+00:00,stac-utils,pystac,gadomski +1709,fix: V2 item collection,PR,closed,jsignell,https://github.com/stac-utils/pystac/pull/1709,2026-04-23T15:31:15+00:00,2026-04-28T14:52:12+00:00,stac-utils,pystac,gadomski +1710,fix: V2 summaries,PR,closed,jsignell,https://github.com/stac-utils/pystac/pull/1710,2026-04-23T18:44:17+00:00,2026-05-05T15:24:13+00:00,stac-utils,pystac,gadomski +1717,fix: V2 add .ext to stac objects,PR,closed,jsignell,https://github.com/stac-utils/pystac/pull/1717,2026-04-30T19:18:24+00:00,2026-05-05T15:25:09+00:00,stac-utils,pystac,gadomski +1718,build(deps): bump the actions-deps group across 1 directory with 3 updates,PR,closed,dependabot[bot],https://github.com/stac-utils/pystac/pull/1718,2026-05-04T18:17:38+00:00,2026-05-08T12:07:22+00:00,stac-utils,pystac,gadomski +1728,build(deps-dev): bump jupyterlab from 4.5.6 to 4.5.7,PR,closed,dependabot[bot],https://github.com/stac-utils/pystac/pull/1728,2026-05-11T21:38:08+00:00,2026-05-11T21:45:02+00:00,stac-utils,pystac,gadomski +1729,fix(ci): build into the subdirectory's `dist/`,PR,closed,gadomski,https://github.com/stac-utils/pystac/pull/1729,2026-05-12T13:20:26+00:00,2026-05-13T15:31:23+00:00,stac-utils,pystac,tylanderson +1730,build(deps): bump mistune from 3.2.0 to 3.2.1,PR,closed,dependabot[bot],https://github.com/stac-utils/pystac/pull/1730,2026-05-14T16:50:20+00:00,2026-05-14T18:30:55+00:00,stac-utils,pystac,gadomski +887,build(deps-dev): bump ruff from 0.15.11 to 0.15.12,PR,closed,dependabot[bot],https://github.com/stac-utils/pystac-client/pull/887,2026-04-27T02:20:38+00:00,2026-04-27T11:56:53+00:00,stac-utils,pystac-client,gadomski diff --git a/reports/plot.py b/reports/plot.py deleted file mode 100644 index cb2f7ae..0000000 --- a/reports/plot.py +++ /dev/null @@ -1,207 +0,0 @@ -import re -from pathlib import Path - -import pandas as pd -import matplotlib.pyplot as plt -from matplotlib.patches import Patch -from matplotlib.ticker import MaxNLocator - -from config import get_current_pi, OBJECTIVES -from settings import TEAM_NAME, TEAM_DISPLAY_NAME, OBJECTIVES_PAGE_URL - - -# Color palette for objectives (cycles if more than 10 objectives) -COLORS = [ - "#e74c3c", # red - "#3498db", # blue - "#2ecc71", # green - "#9b59b6", # purple - "#f39c12", # orange - "#1abc9c", # teal - "#e91e63", # pink - "#00bcd4", # cyan - "#ff5722", # deep orange - "#607d8b", # blue grey -] - - -def get_repo_objectives(pi: str) -> dict: - """ - Build a mapping from repo to list of objectives it belongs to. - - Returns: - Dict mapping "org/repo" to list of (issue_number, title) tuples - """ - repo_to_objectives = {} - for obj in OBJECTIVES.get(pi, []): - for org, repo in obj["repos"]: - key = f"{org}/{repo}" - if key not in repo_to_objectives: - repo_to_objectives[key] = [] - repo_to_objectives[key].append((obj["issue_number"], obj["title"])) - return repo_to_objectives - - -def get_objective_colors(pi: str) -> dict: - """Generate color mapping for objectives in a PI.""" - objectives = OBJECTIVES.get(pi, []) - return { - obj["issue_number"]: COLORS[i % len(COLORS)] for i, obj in enumerate(objectives) - } - - -def get_objective_titles(pi: str) -> dict: - """Get short titles for objectives (strip PI prefix and emojis).""" - objectives = OBJECTIVES.get(pi, []) - titles = {} - length = 100 - for obj in objectives: - title = obj["title"] - # Strip "TEAM PI X.Y Objective N: " prefix if present - if ": " in title: - title = title.split(": ", 1)[1] - # Strip emojis (unicode emoji ranges) - title = re.sub(r"[\U0001F300-\U0001F9FF]", "", title).strip() - # Truncate if too long - if len(title) > length: - title = title[: length - 3] + "..." - titles[obj["issue_number"]] = title - return titles - - -def main(pi: str = None, show_labels: bool = False): - # Default to current PI if not specified - if pi is None: - pi = get_current_pi() - - plot_counts(f"output/{pi}-resolved-issues-prs.csv", pi, title="resolved issues and PRs") - plot_counts(f"output/{pi}-authored-commits.csv", pi, title="authored commits") - - -def plot_counts(csv_filename: str, pi: str, title: str, show_labels: bool = False): - df = pd.read_csv(csv_filename) - - # Build repo to objectives mapping and colors - repo_to_objectives = get_repo_objectives(pi) - objective_colors = get_objective_colors(pi) - - # Get commits per repo with full path - df["full_repo"] = df["organization"] + "/" + df["repository"] - commits_per_repo = df["repository"].value_counts() - full_repo_map = df.groupby("repository")["full_repo"].first().to_dict() - - fig, ax = plt.subplots(1, 1, figsize=(16, 10)) - - # Plot bars with objective-based coloring - for i, (repo, count) in enumerate(commits_per_repo.items()): - full_repo = full_repo_map.get(repo, repo) - objectives = repo_to_objectives.get(full_repo, []) - - if len(objectives) == 0: - # No objective mapping - gray - ax.barh( - i, count, color="#95a5a6", alpha=0.8, edgecolor="black", linewidth=1.2 - ) - elif len(objectives) == 1: - # Single objective - solid color - color = objective_colors.get(objectives[0][0], "#95a5a6") - ax.barh(i, count, color=color, alpha=0.8, edgecolor="black", linewidth=1.2) - else: - # Multiple objectives - split bar by color - width_per_obj = count / len(objectives) - current_x = 0 - for j, (issue_num, _) in enumerate(objectives): - color = objective_colors.get(issue_num, "#95a5a6") - ax.barh( - i, - width_per_obj, - left=current_x, - color=color, - alpha=0.8, - edgecolor="black", - linewidth=1.2, - ) - current_x += width_per_obj - - ax.set_yticks(range(len(commits_per_repo))) - ax.set_yticklabels(commits_per_repo.index) - ax.set_xlabel("Count", fontsize=16, loc="left") - ax.tick_params(axis="y", labelsize=13) - ax.xaxis.set_major_locator(MaxNLocator(integer=True)) - ax.grid(axis="x", alpha=0.3) - - # Add value labels if requested - if show_labels: - for i, v in enumerate(commits_per_repo.values): - ax.text( - v + 0.5, - i, - str(v), - ha="left", - va="center", - fontweight="bold", - fontsize=11, - ) - - plt.subplots_adjust(left=0.3) - - ax.set_title( - f"{pi.upper()} {TEAM_NAME} {title}", - fontsize=24, - fontweight="bold", - ) - - # Legend for objectives with titles - objective_titles = get_objective_titles(pi) - legend_elements = [ - Patch( - facecolor=color, - edgecolor="black", - label=objective_titles.get(num, f"#{num}"), - ) - for num, color in objective_colors.items() - ] - ax.legend( - handles=legend_elements, - loc="upper right", - fontsize=9, - title=f"{pi.upper()} Objectives", - title_fontsize=10, - ) - - # Caveats and link in bottom right of plot area - caveats = ( - "Caveats:\n" - "- Only community-governed open source repositories are tracked\n" - "- Merged PRs counted as one\n" - "- Individual changes may span multiple PRs\n" - "- Split bars indicate repos in multiple objectives\n" - f"- Includes all open source work by {TEAM_DISPLAY_NAME} team members\n\n" - f"Objective details: {OBJECTIVES_PAGE_URL.removeprefix('https://')}" - ) - ax.text( - 1.0, - -0.06, - caveats, - fontsize=8, - style="italic", - horizontalalignment="right", - verticalalignment="top", - transform=ax.transAxes, - bbox=dict( - boxstyle="round,pad=0.3", facecolor="white", edgecolor="gray", alpha=0.8 - ), - ) - - # Save to docs for website - docs_images = Path(__file__).parent.parent / "docs" / "images" - docs_images.mkdir(exist_ok=True) - plt.savefig( - docs_images / Path(csv_filename).name.replace(".csv", ".png"), - bbox_inches="tight", - dpi=150, - ) - - -if __name__ == "__main__": - main() diff --git a/reports/pyproject.toml b/reports/pyproject.toml deleted file mode 100644 index a30a107..0000000 --- a/reports/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[project] -name = "reports" -version = "0.1.0" -description = "Quarterly reporting utilities for the VEDA Science Support team" -readme = "README.md" -requires-python = ">=3.11" -dependencies = [ - "matplotlib>=3.10.3", - "pandas>=2.3.0", - "pygithub>=2.6.1", -] diff --git a/reports/settings.py b/reports/settings.py deleted file mode 100644 index 777b098..0000000 --- a/reports/settings.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Team-specific settings for the reporting system. - -When adopting this reporting structure for a new team, edit the values -below. These are the ONLY values you need to change in the Python -scripts. See docs/adopting.md for the full adoption guide. -""" - -# ── Core identifiers ────────────────────────────────────────────── -GITHUB_ORG = "NASA-IMPACT" -GITHUB_REPO = "science-support" # repo where objective issues live -TEAM_NAME = "Science Support" # short name, used in chart titles -TEAM_DISPLAY_NAME = "VEDA/EODC Science Support" # full name, used in chart caveats -SITE_URL = "nasa-impact.github.io/science-support" # GitHub Pages URL (no https://) - -# ── Authentication ──────────────────────────────────────────────── -TOKEN_ENV_VAR = "GH_PAT" # env var name for the GitHub PAT -# Also update the secret name in .github/workflows/update-reports.yml - -# ── Derived values (do not edit) ────────────────────────────────── -REPO_FULL_NAME = f"{GITHUB_ORG}/{GITHUB_REPO}" -REPO_URL = f"https://github.com/{GITHUB_ORG}/{GITHUB_REPO}" -OBJECTIVES_PAGE_URL = f"https://{SITE_URL}/objectives" diff --git a/team.toml b/team.toml new file mode 100644 index 0000000..05430f5 --- /dev/null +++ b/team.toml @@ -0,0 +1,11 @@ +[team] +name = "Science Support" +display_name = "VEDA/MAAP Science Support" +github_org = "NASA-IMPACT" +github_repo = "science-support" +site_url = "nasa-impact.github.io/science-support" +objectives_page_url = "https://nasa-impact.github.io/science-support/objectives" +token_env_var = "GH_PAT" + +[long_org_name_mapping] +cng = "cloudnativegeo"