Skip to content

Commit 8a39071

Browse files
Copilotfpigeonjrgithub-actions[bot]
authored
feat: auto-fix formatting for web UI commits in CI (#5308)
* chore: add initial implementation plan * feat: auto-fix formatting for web UI commits in CI Co-authored-by: fpigeonjr <4629398+fpigeonjr@users.noreply.github.com> * fix(ci): harden web UI auto-format workflow * style: auto-format code (web UI commit) * fix(ci): preserve current action pins in web UI auto-format workflow --------- Co-authored-by: Frank Pigeon Jr. <frank.pigeonjr@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fpigeonjr <4629398+fpigeonjr@users.noreply.github.com> Co-authored-by: Frank Pigeon Jr. <fpigeon@flexion.us> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 020f579 commit 8a39071

3 files changed

Lines changed: 306 additions & 1 deletion

File tree

.github/pull_request_template.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
_Described what changes in this PR, at a high level_
44

5+
> **Web UI contributors:** If you edited files via the GitHub web interface on a branch in this repository, CI can automatically apply any needed formatting fixes and commit them back to your PR branch. Fork-based PRs may still need local formatting — see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
6+
57
## Issue
68

79
_Add link to issue here_

.github/workflows/ci.yml

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,66 @@ jobs:
111111
lint:
112112
name: Linting
113113
runs-on: ubuntu-latest
114+
permissions:
115+
contents: write
116+
pull-requests: write
114117
steps:
115-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
118+
- name: Check out PR head commit
119+
if: github.event_name == 'pull_request'
120+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
121+
with:
122+
ref: ${{ github.event.pull_request.head.ref }}
123+
repository: ${{ github.event.pull_request.head.repo.full_name }}
124+
token: ${{ secrets.GITHUB_TOKEN }}
125+
126+
- name: Check out push commit
127+
if: github.event_name != 'pull_request'
128+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
129+
130+
- name: Detect commit source
131+
id: detect
132+
run: |
133+
COMMITTER_NAME=$(git log -1 --pretty=format:'%cn')
134+
COMMITTER_EMAIL=$(git log -1 --pretty=format:'%ce')
135+
echo "Last committer: $COMMITTER_NAME <$COMMITTER_EMAIL>"
136+
if [[ "$COMMITTER_NAME" == "GitHub" && "$COMMITTER_EMAIL" == *"noreply@github.com"* ]]; then
137+
echo "source=web-ui" >> $GITHUB_OUTPUT
138+
echo "Web UI commit detected — will auto-format if needed"
139+
else
140+
echo "source=local" >> $GITHUB_OUTPUT
141+
echo "Local commit detected — will check formatting"
142+
fi
143+
144+
- name: Determine formatting strategy
145+
id: strategy
146+
env:
147+
EVENT_NAME: ${{ github.event_name }}
148+
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name || '' }}
149+
CURRENT_REPO: ${{ github.repository }}
150+
SOURCE: ${{ steps.detect.outputs.source }}
151+
run: |
152+
if [[ "$EVENT_NAME" == "pull_request" && "$SOURCE" == "web-ui" && "$HEAD_REPO" == "$CURRENT_REPO" ]]; then
153+
echo "mode=auto-fix" >> $GITHUB_OUTPUT
154+
echo "Same-repo web UI PR detected — will auto-format and push fixes"
155+
else
156+
echo "mode=check" >> $GITHUB_OUTPUT
157+
echo "Formatting will run in check mode"
158+
fi
159+
116160
- uses: ./.github/actions/setup-python
117161

118162
- name: Lint backend
119163
working-directory: ./backend/ops_api
120164
run: pipenv run nox -s lint
121165

166+
# --- For local commits: check formatting (fail build if wrong) ---
122167
- name: Check backend formatting (Black)
168+
if: steps.strategy.outputs.mode == 'check'
123169
working-directory: ./backend/ops_api
124170
run: pipenv run black --config ./pyproject.toml --check ops tests ./noxfile.py ../../performance_tests/locustfile.py
125171

126172
- name: Check backend import sorting (isort)
173+
if: steps.strategy.outputs.mode == 'check'
127174
working-directory: ./backend/ops_api
128175
run: pipenv run isort --settings-file ./pyproject.toml --check-only --filter-files ops tests ./noxfile.py ../../performance_tests/locustfile.py
129176

@@ -132,10 +179,12 @@ jobs:
132179
run: pipenv install --dev
133180

134181
- name: Check data_tools formatting (Black)
182+
if: steps.strategy.outputs.mode == 'check'
135183
working-directory: ./backend/data_tools
136184
run: pipenv run black --config ./pyproject.toml --check .
137185

138186
- name: Check data_tools import sorting (isort)
187+
if: steps.strategy.outputs.mode == 'check'
139188
working-directory: ./backend/data_tools
140189
run: pipenv run isort --settings-file ./pyproject.toml --check-only --filter-files .
141190

@@ -146,5 +195,72 @@ jobs:
146195
run: bun lint
147196

148197
- name: Check frontend formatting
198+
if: steps.strategy.outputs.mode == 'check'
149199
working-directory: ./frontend
150200
run: bun run prettier --check --ignore-unknown 'src/**/*' '!src/uswds/**'
201+
202+
# --- For web UI commits: auto-fix formatting instead of failing ---
203+
- name: Auto-fix backend formatting (Black)
204+
if: steps.strategy.outputs.mode == 'auto-fix'
205+
working-directory: ./backend/ops_api
206+
run: pipenv run black --config ./pyproject.toml ops tests ./noxfile.py ../../performance_tests/locustfile.py
207+
208+
- name: Auto-fix backend import sorting (isort)
209+
if: steps.strategy.outputs.mode == 'auto-fix'
210+
working-directory: ./backend/ops_api
211+
run: pipenv run isort --settings-file ./pyproject.toml --filter-files ops tests ./noxfile.py ../../performance_tests/locustfile.py
212+
213+
- name: Auto-fix data_tools formatting (Black)
214+
if: steps.strategy.outputs.mode == 'auto-fix'
215+
working-directory: ./backend/data_tools
216+
run: pipenv run black --config ./pyproject.toml .
217+
218+
- name: Auto-fix data_tools import sorting (isort)
219+
if: steps.strategy.outputs.mode == 'auto-fix'
220+
working-directory: ./backend/data_tools
221+
run: pipenv run isort --settings-file ./pyproject.toml --filter-files .
222+
223+
- name: Auto-fix frontend formatting (Prettier)
224+
if: steps.strategy.outputs.mode == 'auto-fix'
225+
working-directory: ./frontend
226+
run: bun run prettier --write --ignore-unknown 'src/**/*' '!src/uswds/**'
227+
228+
- name: Commit and push formatting fixes
229+
id: auto-commit
230+
if: steps.strategy.outputs.mode == 'auto-fix'
231+
run: |
232+
git config user.name "github-actions[bot]"
233+
git config user.email "github-actions[bot]@users.noreply.github.com"
234+
git add -A
235+
if git diff --staged --quiet; then
236+
echo "committed=none" >> $GITHUB_OUTPUT
237+
echo "No formatting changes needed"
238+
else
239+
git commit -m "style: auto-format code (web UI commit)"
240+
git push
241+
echo "committed=true" >> $GITHUB_OUTPUT
242+
echo "Formatting fixes committed and pushed"
243+
fi
244+
245+
- name: Comment on PR — formatting auto-fixed
246+
if: |
247+
steps.detect.outputs.source == 'web-ui' &&
248+
steps.auto-commit.outputs.committed == 'true' &&
249+
github.event_name == 'pull_request'
250+
env:
251+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
252+
PR_NUMBER: ${{ github.event.pull_request.number }}
253+
CONTRIBUTING_URL: ${{ github.server_url }}/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/CONTRIBUTING.md
254+
run: |
255+
BODY=$(cat <<EOF
256+
🤖 **Auto-formatting applied!**
257+
258+
I detected that this PR was edited via the GitHub web UI and had some formatting issues. I automatically applied fixes using:
259+
260+
- **Prettier** (frontend)
261+
- **Black** + **isort** (backend)
262+
263+
The fixes have been committed to this branch. For future contributions, consider setting up the [local development environment]($CONTRIBUTING_URL) to run formatters before pushing - it avoids this extra round-trip.
264+
EOF
265+
)
266+
gh pr comment "$PR_NUMBER" --body "$BODY"

CONTRIBUTING.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Contributing to OPRE OPS
2+
3+
Thank you for contributing to OPRE OPS! This guide covers everything you need to know to make successful contributions.
4+
5+
## Table of Contents
6+
7+
- [Quick Start for Web UI Contributors](#quick-start-for-web-ui-contributors)
8+
- [Local Development Setup](#local-development-setup)
9+
- [Code Formatting](#code-formatting)
10+
- [Commit Message Conventions](#commit-message-conventions)
11+
- [Running Tests](#running-tests)
12+
- [Submitting a Pull Request](#submitting-a-pull-request)
13+
14+
---
15+
16+
## Quick Start for Web UI Contributors
17+
18+
If you're making small changes (doc fixes, typo corrections, minor edits) directly through GitHub's web interface, here's what to expect:
19+
20+
**Formatting is auto-fixed for same-repo PRs.** When CI detects a commit made via the GitHub web UI on a pull request branch in this repository, it automatically runs the formatters (Prettier, Black, isort) and commits the fixes. Fork-based PRs still need to run the formatters locally. You'll see a bot comment on your PR if CI applies fixes.
21+
22+
**Linting errors still require attention.** Auto-formatting only fixes code style (whitespace, import order, etc.). If your code has logical linting errors, you'll need to fix those manually.
23+
24+
> **Tip for regular contributors:** Setting up the local dev environment (see below) lets you run formatters before pushing, avoiding the extra auto-format commit round-trip.
25+
26+
---
27+
28+
## Local Development Setup
29+
30+
### Prerequisites
31+
32+
- [Docker](https://docs.docker.com/get-docker/) — required for running the full stack
33+
- [Bun](https://bun.sh/) — JavaScript runtime and package manager (frontend)
34+
- [Python 3.14+](https://www.python.org/downloads/) — backend
35+
- [pipenv](https://pipenv.pypa.io/) — Python dependency manager
36+
37+
### RSA Keys (required for JWT)
38+
39+
```bash
40+
mkdir ~/ops-keys
41+
openssl genrsa -out ~/ops-keys/keypair.pem 2048
42+
openssl rsa -in ~/ops-keys/keypair.pem -pubout -out ~/ops-keys/public.pem
43+
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in ~/ops-keys/keypair.pem -out ~/ops-keys/private.pem
44+
45+
export JWT_PRIVATE_KEY=$(cat ~/ops-keys/private.pem)
46+
export JWT_PUBLIC_KEY=$(cat ~/ops-keys/public.pem)
47+
```
48+
49+
### Install Pre-commit Hooks
50+
51+
Pre-commit hooks run formatters and linters automatically on every commit, so CI stays green.
52+
53+
```bash
54+
pip install pre-commit
55+
pre-commit install
56+
pre-commit install --hook-type commit-msg
57+
```
58+
59+
### Run the Full Stack
60+
61+
```bash
62+
docker compose up --build
63+
```
64+
65+
- **Frontend:** http://localhost:3000
66+
- **Backend API:** http://localhost:8080
67+
68+
---
69+
70+
## Code Formatting
71+
72+
Formatting is enforced in CI. Run these commands locally before pushing to keep CI happy.
73+
74+
### Frontend (Prettier)
75+
76+
```bash
77+
cd frontend
78+
79+
# Check for formatting issues
80+
bun run prettier --check --ignore-unknown 'src/**/*' '!src/uswds/**'
81+
82+
# Auto-fix formatting issues
83+
bun run format
84+
```
85+
86+
### Backend — ops_api (Black + isort)
87+
88+
```bash
89+
cd backend/ops_api
90+
91+
# Check formatting
92+
pipenv run black --config ./pyproject.toml --check ops tests ./noxfile.py
93+
pipenv run isort --settings-file ./pyproject.toml --check-only --filter-files ops tests ./noxfile.py
94+
95+
# Auto-fix formatting
96+
pipenv run black --config ./pyproject.toml ops tests ./noxfile.py
97+
pipenv run isort --settings-file ./pyproject.toml --filter-files ops tests ./noxfile.py
98+
```
99+
100+
### Backend — data_tools (Black + isort)
101+
102+
```bash
103+
cd backend/data_tools
104+
105+
# Check formatting
106+
pipenv run black --config ./pyproject.toml --check .
107+
pipenv run isort --settings-file ./pyproject.toml --check-only --filter-files .
108+
109+
# Auto-fix formatting
110+
pipenv run black --config ./pyproject.toml .
111+
pipenv run isort --settings-file ./pyproject.toml --filter-files .
112+
```
113+
114+
---
115+
116+
## Commit Message Conventions
117+
118+
This project uses [Conventional Commits](https://www.conventionalcommits.org/). Commit messages that don't follow this format will be rejected by CI.
119+
120+
**Format:**
121+
122+
```
123+
<type>: <description>
124+
```
125+
126+
**Common types:**
127+
128+
| Type | When to use |
129+
| ---------- | ----------------------------------------------------- |
130+
| `feat` | A new feature |
131+
| `fix` | A bug fix |
132+
| `docs` | Documentation changes only |
133+
| `style` | Formatting changes (no logic changes) |
134+
| `refactor` | Code restructuring (no feature/bug change) |
135+
| `test` | Adding or updating tests |
136+
| `chore` | Build process, dependency updates, or tooling changes |
137+
138+
**Examples:**
139+
140+
```bash
141+
git commit -m "feat: add export button to budget line items table"
142+
git commit -m "fix: resolve null pointer on agreement detail page"
143+
git commit -m "docs: add web contributor guide to CONTRIBUTING.md"
144+
```
145+
146+
---
147+
148+
## Running Tests
149+
150+
### Frontend unit tests
151+
152+
```bash
153+
cd frontend
154+
bun run test --watch=false
155+
```
156+
157+
### Backend unit tests
158+
159+
```bash
160+
cd backend/ops_api
161+
pipenv run pytest
162+
```
163+
164+
### End-to-end tests (requires running Docker stack)
165+
166+
```bash
167+
cd frontend
168+
bun run test:e2e
169+
```
170+
171+
---
172+
173+
## Submitting a Pull Request
174+
175+
1. Fork the repository and create your branch from `main`.
176+
2. Make your changes and ensure tests pass locally.
177+
3. Run the formatters (see [Code Formatting](#code-formatting)) before pushing.
178+
4. Open a pull request against `main`.
179+
5. Fill out the PR template — especially the **A11y impact** and **Definition of Done** sections.
180+
181+
PRs require review from a CODEOWNER before merging. CI must be green (all checks passing).
182+
183+
---
184+
185+
## Questions?
186+
187+
Open a [GitHub Discussion](https://github.com/HHS/OPRE-OPS/discussions) or reach out to the team via the project's communication channels.

0 commit comments

Comments
 (0)