-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Jira integration, sync improvements, and bot filter cleanup #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
21560ca
60ae478
db54d61
ecd08f9
6fac5f9
4063332
8811841
f9b2098
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Git & Pull Requests | ||
|
|
||
| - Always open PRs against **Bandwidth/github-pulse** (`origin`). Never use `ViktorBilokin/github-pulse` (`upstream`). | ||
| - Push feature branches to `origin` and use `gh pr create --repo Bandwidth/github-pulse --base main --head <branch>`. | ||
|
|
||
| ## Commands | ||
|
|
||
| ### Development | ||
|
|
||
| ```bash | ||
| make start-dev # Full dev mode: PostgreSQL + backend (port 8080) + frontend HMR (port 3000) | ||
| make dev # Start PostgreSQL only (then run backend/frontend manually) | ||
| make start # Production mode: single JAR serving frontend at port 8080 | ||
| ``` | ||
|
|
||
| ### Backend (Maven) | ||
|
|
||
| ```bash | ||
| cd backend | ||
| ./mvnw spring-boot:run # Run backend | ||
| ./mvnw test # Run Cucumber BDD integration tests | ||
| ./mvnw package -DskipTests # Build production JAR | ||
| ``` | ||
|
|
||
| ### Frontend (npm) | ||
|
|
||
| ```bash | ||
| cd frontend | ||
| npm run dev # Vite dev server on port 3000 | ||
| npm run build # TypeScript compile + Vite build → dist/ | ||
| npm run lint # ESLint | ||
| ``` | ||
|
|
||
| ### Docker | ||
|
|
||
| ```bash | ||
| make up # Start full stack via Docker Compose | ||
| make down # Stop stack | ||
| make logs # View logs | ||
| ``` | ||
|
|
||
| ### Cleanup | ||
|
|
||
| ```bash | ||
| make clean # Clean Maven build, node_modules/.vite, and static resources | ||
| make stop # Stop all services and kill ports 8080, 3000, 5432 | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| **GitHub Pulse** is a GitHub activity analytics dashboard. The stack is Spring Boot 3.3.5 (Java 21) backend with a React + TypeScript + Vite frontend. | ||
|
|
||
| ### Request Flow | ||
|
|
||
| ``` | ||
| Browser (port 3000 dev / 80 prod) | ||
| → /api/* proxied to Spring Boot (port 8080) | ||
| → Services → PostgreSQL + GitHub API + Jira API + Anthropic API | ||
| ``` | ||
|
|
||
| In production, the frontend is built and embedded into the Spring Boot JAR as static resources (served at `/`). | ||
|
|
||
| ### Backend Structure (`backend/src/main/java/com/githubpulse/`) | ||
|
|
||
| - **`controller/`** — REST controllers: `DashboardController`, `AdminController`, `FiltersController`, `JiraController` | ||
| - **`service/github/`** — GitHub sync via GraphQL (`GitHubGraphQlSyncClient`) and REST (`GitHubPatClient`) | ||
| - **`service/dashboard/`** — Aggregates metrics (top contributors, reviewers, PR details) | ||
| - **`service/jira/`** — Jira REST API client + Claude-powered ticket quality scoring via `TicketQualityService` | ||
| - **`scheduler/GitHubSyncScheduler.java`** — Hourly cron sync with ShedLock distributed locking | ||
| - **`repository/`** — Spring Data JPA repositories for all entities | ||
|
|
||
| ### Frontend Structure (`frontend/src/`) | ||
|
|
||
| - **`pages/`** — `DashboardPage`, `AdminPage`, `ConfigPage`, `SyncPage`, `JiraPage` | ||
| - **`components/`** — Reusable UI components | ||
| - Vite proxies `/api` → `http://localhost:8080` in dev | ||
|
|
||
| ### Database | ||
|
|
||
| PostgreSQL 16 with Flyway migrations in `operations/database/`: | ||
| - `V1__initial_schema.sql` — Core tables: `repositories`, `teams`, `contributors`, `team_members`, `pull_requests`, `reviews`, `comments`, `sync_state`, `shedlock` | ||
| - `V2__create_app_settings_table.sql` — `app_settings` key-value store | ||
|
|
||
| Hibernate is set to `validate` mode — schema changes must go through Flyway migrations. | ||
|
|
||
| ### Key Configuration (`backend/src/main/resources/application.yml`) | ||
|
|
||
| All values are environment-driven: | ||
|
|
||
| | Variable | Purpose | | ||
| |---|---| | ||
| | `GITHUB_TOKEN` | GitHub PAT (required; scopes: `repo`, `read:org`) | | ||
| | `ANTHROPIC_API_KEY` | Claude API key for Jira quality scoring | | ||
| | `DB_HOST/PORT/NAME/USERNAME/PASSWORD` | PostgreSQL connection | | ||
| | `SYNC_CRON` | Sync schedule (default: `0 0 * * * *` — hourly) | | ||
| | `SPRING_FLYWAY_LOCATIONS` | Path to migration SQL files | | ||
|
|
||
| ### Backend Tests | ||
|
|
||
| Cucumber BDD integration tests with H2 (PostgreSQL compatibility mode). Feature files live in `backend/src/test/resources/features/`. Run with `./mvnw test`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,109 @@ | ||
| # github-pulse | ||
| <!-- test: sample PR to validate CI workflow --> | ||
| # GitHub Pulse | ||
|
|
||
| GitHub Pulse is an internal engineering dashboard that aggregates GitHub activity across your organization's repositories. It gives teams visibility into pull requests, contributor metrics, and delivery health — with optional Jira integration to surface ticket quality and epic progress alongside code activity. | ||
|
|
||
| ## Features | ||
|
|
||
| - **Dashboard** — PR activity, merge rates, review throughput, and contributor stats across tracked repositories | ||
| - **Jira Integration** — View epics and stories, AI-powered ticket quality scoring, and acceptance criteria improvement suggestions | ||
| - **Sync Management** — Configure sync schedules, trigger manual syncs, and inspect sync history | ||
| - **Bot Filter** — Suppress bot-generated PRs from metrics | ||
| - **Configuration** — Manage GitHub tokens, tracked repositories, team members, and Jira connection settings | ||
|
|
||
| ## Technology Stack | ||
|
|
||
| | Layer | Technology | | ||
| |---|---| | ||
| | Frontend | React 19, TypeScript, Vite, Tailwind CSS 4, shadcn/ui, Recharts, TanStack Query | | ||
| | Backend | Java 21, Spring Boot 3.3, Spring Data JPA | | ||
| | Database | PostgreSQL 16, Flyway migrations | | ||
| | AI | Anthropic Claude (ticket quality analysis) | | ||
| | Testing | Cucumber (BDD integration tests) | | ||
| | Scheduling | ShedLock (distributed cron) | | ||
| | Packaging | Docker, Docker Compose | | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Java 21+ | ||
| - Node.js 20+ | ||
| - Docker (for PostgreSQL) | ||
| - A GitHub Personal Access Token with `repo` and `read:org` scopes | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| Copy `.env.example` to `.env` and fill in your values: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| | Variable | Required | Description | | ||
| |---|---|---| | ||
| | `GITHUB_TOKEN` | Yes | GitHub PAT with `repo` and `read:org` scopes | | ||
| | `ANTHROPIC_API_KEY` | No | Enables AI ticket quality analysis in Jira view | | ||
|
|
||
| Database connection defaults (`DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USERNAME`, `DB_PASSWORD`) are pre-configured for the Docker Compose stack and do not need to be set for local development. | ||
|
|
||
| ## Running Locally | ||
|
|
||
| ### Option 1 — Dev mode (recommended, frontend HMR on `:3000`) | ||
|
|
||
| ```bash | ||
| make start-dev | ||
| ``` | ||
|
|
||
| - Frontend: http://localhost:3000 | ||
| - Backend API: http://localhost:8080 | ||
|
|
||
| ### Option 2 — Production-like (single server on `:8080`) | ||
|
|
||
| ```bash | ||
| make start | ||
| ``` | ||
|
|
||
| - UI + API: http://localhost:8080 | ||
|
|
||
| ### Option 3 — Docker Compose (full containerized stack) | ||
|
|
||
| ```bash | ||
| docker compose up -d --build | ||
| ``` | ||
|
|
||
| ### Stopping | ||
|
|
||
| ```bash | ||
| make stop | ||
| ``` | ||
|
|
||
| ## Building | ||
|
|
||
| Build a production JAR with the frontend embedded: | ||
|
|
||
| ```bash | ||
| make build | ||
| ``` | ||
|
|
||
| The artifact is written to `backend/target/github-pulse-*.jar`. | ||
|
|
||
| ## Running Tests | ||
|
|
||
| ```bash | ||
| cd backend && ./mvnw test | ||
| ``` | ||
|
|
||
| Tests use Cucumber BDD scenarios covering the dashboard, settings, team management, repository management, and sync workflows. | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| github-pulse/ | ||
| ├── backend/ # Spring Boot application | ||
| │ ├── src/main/java/ # Controllers, services, repositories, domain | ||
| │ └── src/test/java/ # Cucumber step definitions and runners | ||
| ├── frontend/ # React + Vite SPA | ||
| │ └── src/pages/ # Dashboard, Config, Jira, Sync, Admin | ||
| ├── operations/ | ||
| │ └── database/ # Flyway SQL migrations | ||
| ├── docker-compose.yml | ||
| └── Makefile # Dev lifecycle commands | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package com.githubpulse.controller; | ||
|
|
||
| import com.githubpulse.service.jira.JiraClient; | ||
| import com.githubpulse.service.jira.JiraEpicDto; | ||
| import com.githubpulse.service.jira.JiraIssueDetailDto; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/jira") | ||
| @CrossOrigin(origins = "*") | ||
| public class JiraController { | ||
|
|
||
| private static final Pattern JIRA_KEY_PATTERN = Pattern.compile("^[A-Z][A-Z0-9]{0,9}-\\d+$"); | ||
|
|
||
| private final JiraClient jiraClient; | ||
|
|
||
| public JiraController(JiraClient jiraClient) { | ||
| this.jiraClient = jiraClient; | ||
| } | ||
|
|
||
| @GetMapping("/epics") | ||
| public List<JiraEpicDto> getEpics() { | ||
| return jiraClient.getEpicsWithChildren(); | ||
| } | ||
|
|
||
| @GetMapping("/issue/{key}") | ||
| public JiraIssueDetailDto getIssueDetail(@PathVariable String key) { | ||
| if (key == null || !JIRA_KEY_PATTERN.matcher(key).matches()) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot is it secure? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the endpoint is secure. Here's the full SSRF protection in place:
CodeQL reports 0 alerts after the change. |
||
| throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid Jira issue key"); | ||
| } | ||
| return jiraClient.getIssueDetail(key); | ||
bwappsec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
Comment on lines
+27
to
+38
|
||
|
|
||
| @GetMapping("/debug") | ||
| public Map<String, Object> debug() { | ||
| return jiraClient.debug(); | ||
| } | ||
|
Comment on lines
+40
to
+43
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change modifies the manual sync response strings and behavior (completed repos now sync incrementally). Existing Cucumber scenarios currently assert the old wording like "No pending" / "1 pending"; consider either keeping backward-compatible response text or updating the BDD tests accordingly so CI doesn't break.