Skip to content

CNTRLPLANE-3043: Add jira-agent performance dashboard - #8033

Draft
bryan-cox wants to merge 18 commits into
openshift:mainfrom
bryan-cox:CNTRLPLANE-3043
Draft

CNTRLPLANE-3043: Add jira-agent performance dashboard#8033
bryan-cox wants to merge 18 commits into
openshift:mainfrom
bryan-cox:CNTRLPLANE-3043

Conversation

@bryan-cox

@bryan-cox bryan-cox commented Mar 23, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

Adds a self-contained web dashboard and data pipeline for tracking jira-agent performance metrics on a dedicated HostedCluster. This gives the team visibility into merge rates, Claude API costs, PR lifecycle times, and review comment quality.

Components

  • Dashboard (Deployment): REST API server + vanilla HTML/JS/CSS frontend with Chart.js, backed by SQLite (WAL mode) on a PVC. Served behind an OpenShift OAuth proxy sidecar.
  • Scraper (CronJobs): Two daily jobs — scraper-prow pulls Prow job artifacts from GCS, scraper-github fetches PR metadata and review comments via a dedicated GitHub App.
  • Database: 5-table SQLite schema (job_runs, issues, phase_metrics, review_comments, pr_complexity).

Frontend Features

  • Overview page: Summary cards (total issues, merge rate, total/avg cost), 5 charts (status breakdown, cost per issue, PR time to merge, top reviewers, comment severity), paginated recent activity feed with direct GitHub comment links
  • Issues page: Sortable table with PR status, dates, lines changed, cost, duration
  • Comments page: Filterable comment list with severity/topic charts, pattern table, markdown report download
  • Issue detail page: Per-issue phase breakdown, cost metrics, comment classification editor
  • Collapsible sidebar navigation with hamburger toggle (state persists via localStorage)

Security Hardening

  • Authentication: OAuth proxy sidecar — users must authenticate via OpenShift OAuth
  • Authorization: openshift-delegate-urls requires get on services in namespace; system:auth-delegator ClusterRoleBinding for token review
  • TLS: Route uses reencrypt termination with auto-generated service-serving-cert
  • Network: NetworkPolicy restricts dashboard ingress to OpenShift router only; scrapers deny all ingress
  • Containers: Non-root (USER 1001), readOnlyRootFilesystem, allowPrivilegeEscalation: false, all capabilities dropped
  • XSS prevention: Shared escapeHTML() applied to all innerHTML interpolation
  • SQL injection: All queries use parameterized placeholders
  • Input validation: PATCH body limited to 1KB, severity/topic validated against allowlists
  • Error handling: Internal errors logged server-side; generic messages returned to clients
  • Secrets: Excluded from kustomization and .gitignore; private key mounted at 0400
  • Cache control: no-cache, must-revalidate on static files to prevent stale JS/CSS

Deployment

All Kubernetes manifests are in deploy/ managed by kustomize. Secrets are created manually from the provided secrets.yaml.example template. Full restore-from-scratch instructions are in the README.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3043

Special notes for your reviewer:

  • This is a standalone subdirectory (jira-agent-dashboard/) with its own go.mod — it does not affect the main HyperShift module or any existing code.
  • The vendor/ directory is not committed; dependencies are managed via go.mod.
  • The dashboard is currently deployed and running on a dedicated HostedCluster.
  • The scraper-complexity CronJob was removed — complexity analysis may be re-added later.
  • Review comment classification (severity/topic) can be done manually via the UI or automated via Claude API (not yet implemented).

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • New Features

    • Full dashboard with REST API and static web UI (Overview, Issues, Comments), comment classification editing, PR complexity analysis, scheduled scrapers and backfill jobs.
  • Chores

    • Container build/runtime spec, build targets (Makefile), OpenShift/Kubernetes manifests (namespace, PVC, Service/Route, Deployment, CronJobs, NetworkPolicy, RBAC), example secrets, and updated .gitignore.
  • Documentation

    • Comprehensive README with architecture, deployment, API, security, and developer instructions.
  • Tests

    • Extensive unit and integration tests covering API, DB schema/store, scraper, complexity parsing, orchestrator, and end-to-end flows.

@openshift-ci-robot

Copy link
Copy Markdown

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 23, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references CNTRLPLANE-3043 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Summary

  • Adds a web dashboard + scraper for tracking jira-agent outcome quality and operational metrics
  • Scraper CronJob pulls data from GCS artifacts and GitHub API daily
  • Dashboard serves a vanilla HTML/JS frontend with Chart.js over a REST API
  • SQLite database (WAL mode) on a shared PVC
  • Deployable via kubectl apply -k jira-agent-dashboard/deploy/

Components

  • Go backend: REST API server + scraper with SQLite storage
  • Database: 5-table schema (job_runs, issues, phase_metrics, review_comments, pr_complexity)
  • Scraper: GCS artifact parser, GitHub API client, complexity analyzer (gocyclo/gocognit)
  • Dashboard: 4 pages — Trends, Issues table, Issue detail, Comment classification summary
  • Frontend: Vanilla HTML/JS + Chart.js
  • K8s manifests: Namespace, PVC, Deployment+Service, Route, CronJob, Kustomization

Not yet implemented

  • Review comment classification via ephemeral Claude Code pods (skill created in ai-helpers: code-review:classify-review-comment)

Test plan

  • Unit tests pass for all packages (go test ./...)
  • Integration test covers full API flow (go test ./internal/ -tags=integration)
  • Build succeeds (make build)
  • Container image builds (make image)
  • Deploy to HostedCluster and verify dashboard loads

Jira: CNTRLPLANE-3043

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 23, 2026
@openshift-ci

openshift-ci Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request adds a complete JIRA Agent Dashboard: a Go-based scraper and orchestrator, GitHub App auth, GCS reader, PR complexity analyzer, SQLite-backed store with schema and migrations, REST API server, API handlers/responses, a static web frontend (HTML/CSS/JS), Containerfile and Makefile, OpenShift/Kubernetes kustomize manifests (Namespace, PVC, Deployment with oauth-proxy, Route, CronJobs, NetworkPolicies, RBAC, secrets example), and extensive unit and integration tests.

Sequence Diagram(s)

sequenceDiagram
    participant Scraper as Scraper Pod
    participant GCS as Google Cloud Storage
    participant GitHub as GitHub API
    participant Complexity as Complexity Analyzer
    participant DB as SQLite DB (PVC)

    Scraper->>GCS: List builds
    GCS-->>Scraper: Build IDs
    Scraper->>GCS: Read build-log.txt / timestamps
    GCS-->>Scraper: Build log + timestamps
    Scraper->>DB: Insert JobRun, Issue, PhaseMetric
    DB-->>Scraper: ACK
    Scraper->>GitHub: GetPR(owner,repo,number)
    GitHub-->>Scraper: PR metadata & diff stats
    Scraper->>GitHub: GetPRReviewComments()
    GitHub-->>Scraper: Review & issue comments
    Scraper->>DB: Upsert PRComplexity, Insert ReviewComments
    DB-->>Scraper: ACK
    Scraper->>Complexity: AnalyzePR (clone & run tools)
    Complexity-->>Scraper: Complexity deltas
    Scraper->>DB: Update complexity deltas
Loading
sequenceDiagram
    participant Browser as Browser/Client
    participant OAuth as OAuth Proxy
    participant API as Dashboard API Server
    participant Store as SQLite DB (PVC)

    Browser->>OAuth: GET / (or /api/...)
    OAuth->>OAuth: Validate token
    OAuth->>API: Forward request
    API->>Store: Query issues / trends / comments
    Store-->>API: Data rows
    API-->>OAuth: JSON response
    OAuth-->>Browser: Authenticated response
    Browser->>Browser: Render charts, tables, pages
    Browser->>API: PATCH /api/comments/{id} (edit)
    API->>Store: Update classification (mark human_override)
    Store-->>API: Updated row
    API-->>Browser: Return updated comment
Loading
sequenceDiagram
    participant User as User (Browser)
    participant Frontend as Frontend JS
    participant API as API Server
    participant DB as SQLite DB

    User->>Frontend: Open issue detail
    Frontend->>API: GET /api/issues/{id}
    API->>DB: Fetch issue, phases, comments, complexity
    DB-->>API: Issue detail JSON
    API-->>Frontend: Issue + phases + comments
    Frontend->>Frontend: Render phase chart & comments
    User->>Frontend: Edit comment classification
    Frontend->>API: PATCH /api/comments/{id}
    API->>DB: Update comment classification (human_override=true)
    DB-->>API: Updated comment
    API-->>Frontend: Updated comment detail
    Frontend->>Frontend: Update UI
Loading
🚥 Pre-merge checks | ✅ 11 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the primary change: adding a jira-agent performance dashboard. It is concise, specific, and directly describes the main deliverable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed This pull request uses standard Go testing framework with testing.T and t.Run(), not Ginkgo BDD syntax. The custom check addresses Ginkgo test naming conventions which are not applicable here.
Test Structure And Quality ✅ Passed Custom check for Ginkgo test structures not applicable; PR uses standard Go testing with proper patterns including t.Helper() and t.Cleanup().
Microshift Test Compatibility ✅ Passed The custom check for new Ginkgo e2e tests applies only to Ginkgo-specific patterns (It(), Describe(), Context(), When()). The grep results were false positives matching context parameter names in standard library function signatures, not Ginkgo framework patterns. The PR contains only standard Go unit and integration tests with func Test*(t *testing.T) signatures and no Ginkgo/Gomega imports, so the MicroShift compatibility check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This pull request does not introduce any Ginkgo e2e tests. The 9 test files added are all standard Go unit and integration tests using the func Test*(t *testing.T) pattern with in-memory SQLite databases and httptest for HTTP handler testing—no Ginkgo imports or BDD-style constructs.
Topology-Aware Scheduling Compatibility ✅ Passed Dashboard Deployment with 1 replica and CronJob scrapers contain no affinity constraints, nodeSelector targeting control-plane, pod anti-affinity, or topologySpreadConstraints, making them compatible with SNO, Two-Node, HyperShift, and standard HA topologies.
Ote Binary Stdout Contract ✅ Passed Application binaries use only Go's log package (stderr), no stdout writes. Not OTE test binaries.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed This PR does not add any Ginkgo e2e tests. All test files use standard Go testing patterns with func Test*(*testing.T) functions and contain no Ginkgo patterns like It(), Describe(), Context(), or When() blocks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@openshift-ci

openshift-ci Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 23, 2026
@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.51%. Comparing base (b5f3906) to head (c392dea).
⚠️ Report is 465 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8033   +/-   ##
=======================================
  Coverage   44.50%   44.51%           
=======================================
  Files         774      774           
  Lines       96980    96997   +17     
=======================================
+ Hits        43164    43179   +15     
- Misses      50828    50830    +2     
  Partials     2988     2988           

see 2 files with indirect coverage changes

Flag Coverage Δ
cmd-support 38.39% <ø> (ø)
cpo-hostedcontrolplane 47.22% <ø> (+0.02%) ⬆️
cpo-other 45.25% <ø> (ø)
hypershift-operator 54.45% <ø> (+<0.01%) ⬆️
other 32.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@openshift-ci-robot

openshift-ci-robot commented Apr 20, 2026

Copy link
Copy Markdown

@bryan-cox: This pull request references CNTRLPLANE-3043 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Adds a self-contained web dashboard and data pipeline for tracking jira-agent performance metrics on a dedicated HostedCluster. This gives the team visibility into merge rates, Claude API costs, PR lifecycle times, and review comment quality.

Components

  • Dashboard (Deployment): REST API server + vanilla HTML/JS/CSS frontend with Chart.js, backed by SQLite (WAL mode) on a PVC. Served behind an OpenShift OAuth proxy sidecar.
  • Scraper (CronJobs): Two daily jobs — scraper-prow pulls Prow job artifacts from GCS, scraper-github fetches PR metadata and review comments via a dedicated GitHub App.
  • Database: 5-table SQLite schema (job_runs, issues, phase_metrics, review_comments, pr_complexity).

Frontend Features

  • Overview page: Summary cards (total issues, merge rate, total/avg cost), 5 charts (status breakdown, cost per issue, PR time to merge, top reviewers, comment severity), paginated recent activity feed with direct GitHub comment links
  • Issues page: Sortable table with PR status, dates, lines changed, cost, duration
  • Comments page: Filterable comment list with severity/topic charts, pattern table, markdown report download
  • Issue detail page: Per-issue phase breakdown, cost metrics, comment classification editor
  • Collapsible sidebar navigation with hamburger toggle (state persists via localStorage)

Security Hardening

  • Authentication: OAuth proxy sidecar — users must authenticate via OpenShift OAuth
  • Authorization: openshift-delegate-urls requires get on services in namespace; system:auth-delegator ClusterRoleBinding for token review
  • TLS: Route uses reencrypt termination with auto-generated service-serving-cert
  • Network: NetworkPolicy restricts dashboard ingress to OpenShift router only; scrapers deny all ingress
  • Containers: Non-root (USER 1001), readOnlyRootFilesystem, allowPrivilegeEscalation: false, all capabilities dropped
  • XSS prevention: Shared escapeHTML() applied to all innerHTML interpolation
  • SQL injection: All queries use parameterized placeholders
  • Input validation: PATCH body limited to 1KB, severity/topic validated against allowlists
  • Error handling: Internal errors logged server-side; generic messages returned to clients
  • Secrets: Excluded from kustomization and .gitignore; private key mounted at 0400
  • Cache control: no-cache, must-revalidate on static files to prevent stale JS/CSS

Deployment

All Kubernetes manifests are in deploy/ managed by kustomize. Secrets are created manually from the provided secrets.yaml.example template. Full restore-from-scratch instructions are in the README.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3043

Special notes for your reviewer:

  • This is a standalone subdirectory (jira-agent-dashboard/) with its own go.mod — it does not affect the main HyperShift module or any existing code.
  • The vendor/ directory is not committed; dependencies are managed via go.mod.
  • The dashboard is currently deployed and running on a dedicated HostedCluster.
  • The scraper-complexity CronJob was removed — complexity analysis may be re-added later.
  • Review comment classification (severity/topic) can be done manually via the UI or automated via Claude API (not yet implemented).

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

Release Notes

  • New Features

  • Dashboard application with REST API for tracking development issues, pull requests, and review comments

  • Web-based UI for viewing issue trends, PR metrics, review comments, and cost analysis

  • Comment classification system with AI-assisted severity and topic labeling

  • Automated data collection from GitHub and Prow job systems via scheduled tasks

  • Code complexity analysis for pull requests

  • Chores

  • Added container build specification and Kubernetes deployment manifests for OpenShift

  • Added build tooling and project structure (Makefile, Go module definition, .gitignore)

  • Added example configuration for required deployment secrets

  • Documentation

  • Added comprehensive README covering architecture, deployment, API reference, and security controls

  • Tests

  • Added integration and unit tests for API handlers, database operations, and external service integrations

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (8)
jira-agent-dashboard/internal/db/models.go-53-54 (1)

53-54: ⚠️ Potential issue | 🟡 Minor

Topic value documentation is already out of sync.

Line [54] documents only five topic values, but the frontend already supports additional ones (e.g., architecture_design, security, ci, approval, process, unclassified). Please update this comment or move allowed values to shared constants to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/models.go` around lines 53 - 54, The inline
comment for the Topic field is out of date; update the comment on the Topic
field in models.go to list the current allowed topic values (including
architecture_design, security, ci, approval, process, unclassified, etc.)
or—preferably—extract those allowed topic strings into a shared constant set
(e.g., a package-level slice or const block like Topic* constants) and reference
that constant from both backend and frontend; change the Topic field comment to
mention the shared constants (or canonical source) so the doc won’t drift.
jira-agent-dashboard/web/css/style.css-173-175 (1)

173-175: ⚠️ Potential issue | 🟡 Minor

Mobile layout can retain an unintended 48px content offset when nav is collapsed.

Add a mobile-specific override for the collapsed sibling selector.

📱 Suggested CSS patch
 `@media` (max-width: 768px) {
   nav {
     width: 100%;
     height: auto;
     position: relative;
   }

   main {
     margin-left: 0;
   }
+
+  nav.collapsed ~ main {
+    margin-left: 0;
+  }

Also applies to: 909-918

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/css/style.css` around lines 173 - 175, The collapsed
nav sibling rule "nav.collapsed ~ main { margin-left: 48px; }" causes a
persistent 48px offset on small screens; add a mobile-specific override (using
an appropriate media query for your mobile breakpoint) that resets the
margin-left to 0 for "nav.collapsed ~ main" so collapsed navigation does not
shift content on phones, and apply the same override to the duplicate
collapsed-nav rules referenced around lines 909-918 (the same selector
occurrences) to ensure consistent mobile behavior.
jira-agent-dashboard/web/issue.html-14-16 (1)

14-16: ⚠️ Potential issue | 🟡 Minor

Add active nav state for issue-detail context.

Lines 14-16 don’t mark the current section as active, so page context is less clear for users.

🎯 Suggested fix
-        <li><a href="issues.html">Issues</a></li>
+        <li><a href="issues.html" class="active">Issues</a></li>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/issue.html` around lines 14 - 16, On the
issue-detail page (issue.html) mark the Issues nav item as active by changing
the <li><a href="issues.html">Issues</a></li> entry to include the active
state—add class="active" to the <li> and aria-current="page" to the <a> (e.g.,
<li class="active"><a href="issues.html" aria-current="page">Issues</a></li>);
if nav is rendered via a template, set the active flag for the "Issues" item
when rendering issue.html so the same attributes are injected dynamically.
jira-agent-dashboard/web/issues.html-62-63 (1)

62-63: ⚠️ Potential issue | 🟡 Minor

Fix the loading-row column span.

The table defines 13 columns, but Line 63 spans 12, so the loading/empty state will render misaligned once the action column is present.

🩹 Proposed fix
-            <tr><td colspan="12" style="text-align:center;">Loading...</td></tr>
+            <tr><td colspan="13" style="text-align:center;">Loading...</td></tr>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/issues.html` around lines 62 - 63, The loading/empty
placeholder row under the tbody with id="issues-tbody" uses colspan="12" but the
table actually defines 13 columns; update the loading/empty <tr><td ...> cell to
use colspan="13" (the row rendering the "Loading..." state) so it aligns
correctly when the action column is present.
jira-agent-dashboard/internal/api/handlers.go-59-60 (1)

59-60: ⚠️ Potential issue | 🟡 Minor

Duplicate assignment: both AvgDurationMs and AvgMergeDuration use the same source.

Both fields are set to t.AvgDurationMs. If these fields represent different metrics, one assignment may be incorrect. If they're aliases for backward compatibility, consider adding a comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/api/handlers.go` around lines 59 - 60, The two
fields AvgDurationMs and AvgMergeDuration are both assigned from
t.AvgDurationMs; update the assignment in the struct literal so AvgMergeDuration
uses the correct source (e.g., t.AvgMergeDuration) if it represents a different
metric, or add a clarifying comment next to the AvgMergeDuration line indicating
it is intentionally an alias of AvgDurationMs for backward compatibility; locate
the assignment using the symbols AvgDurationMs, AvgMergeDuration and the
variable t to make the change.
jira-agent-dashboard/README.md-230-231 (1)

230-231: ⚠️ Potential issue | 🟡 Minor

Documentation lists incomplete topic values.

The allowedTopics map in internal/api/handlers.go (lines 23-35) includes architecture_design and security, but these are missing from the README documentation.

📝 Suggested fix
-**Topic:** `style`, `logic_bug`, `test_gap`, `api_design`, `documentation`, `ci`, `approval`, `process`, `unclassified`
+**Topic:** `style`, `logic_bug`, `test_gap`, `api_design`, `architecture_design`, `security`, `documentation`, `ci`, `approval`, `process`, `unclassified`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/README.md` around lines 230 - 231, The README’s Topic
list is missing values declared in the code; update the README documentation to
include the `architecture_design` and `security` topics that are present in the
allowedTopics map in internal/api/handlers.go. Open the README section that
enumerates Topic values and add those two entries with the same naming/casing
used by allowedTopics so the docs match the code (ensure any examples or table
rows referencing topics are updated accordingly).
jira-agent-dashboard/web/js/issues.js-33-33 (1)

33-33: ⚠️ Potential issue | 🟡 Minor

Fix the empty-state colspan.

jira-agent-dashboard/web/issues.html defines 13 headers, including the trailing action column, so this row should span 13 cells or the empty state stays misaligned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/issues.js` at line 33, The empty-state row
inserted via tbody.innerHTML currently uses colspan="12" which misaligns the
empty message because the table has 13 headers (including the action column);
update the HTML string in the assignment to tbody.innerHTML (the line setting
'<tr><td colspan="12"...') to use colspan="13" so the placeholder spans all
columns and aligns with the table headers.
jira-agent-dashboard/web/js/issues.js-77-79 (1)

77-79: ⚠️ Potential issue | 🟡 Minor

First click on a new column sorts descending.

For a brand-new column, currentDirection starts as 'asc' and is immediately flipped to 'desc', so the initial click does the opposite of the usual ascending-first behavior.

Suggested fix
 function sortTable(column, getValue) {
-  const currentDirection = sortColumn === column ? sortDirection : 'asc';
-  const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
+  const newDirection =
+    sortColumn === column && sortDirection === 'asc' ? 'desc' : 'asc';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/issues.js` around lines 77 - 79, The click
handler flips the computed currentDirection immediately, so when sortColumn !==
column you default currentDirection to 'asc' and then flip it to 'desc' on first
click; change the logic so a new column starts sorting ascending: compute
newDirection based on whether the clicked column matches sortColumn — if it
does, flip sortDirection (sortDirection === 'asc' ? 'desc' : 'asc'), otherwise
set newDirection to 'asc'; update the code that sets
currentDirection/newDirection (referencing sortColumn, sortDirection,
currentDirection, newDirection) accordingly.
🧹 Nitpick comments (8)
jira-agent-dashboard/deploy/rbac.yaml (1)

4-4: Prefer a globally unique ClusterRoleBinding name.

Because this is cluster-scoped, a generic name like dashboard-auth-delegator can collide with other installs. Consider prefixing with app/namespace identity (e.g., jira-agent-dashboard-auth-delegator).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/deploy/rbac.yaml` at line 4, The ClusterRoleBinding
currently uses a generic cluster-scoped name "dashboard-auth-delegator"; change
metadata.name to a globally unique name (e.g.,
"jira-agent-dashboard-auth-delegator") to avoid collisions, and update any
references to that binding elsewhere in manifests; locate the ClusterRoleBinding
resource (metadata.name) in rbac.yaml and rename it consistently across the
deployment/helm templates or other YAMLs that refer to
"dashboard-auth-delegator".
jira-agent-dashboard/internal/scraper/gcs_test.go (1)

97-99: Use epsilon-based float assertions in parser tests.

Direct != checks against floating-point values can make tests brittle. The test suite has multiple exact comparisons (lines 97, 127, 142) that should use tolerance-based assertions instead.

Replace with epsilon comparisons:

Suggested fix
+import "math"
@@
-	if p1.TotalCostUSD != 2.44697175 {
+	if math.Abs(p1.TotalCostUSD-2.44697175) > 1e-9 {
 		t.Errorf("Phase 1 cost = %f, want 2.44697175", p1.TotalCostUSD)
 	}
@@
-	if p2.TotalCostUSD != 2.0867235 {
+	if math.Abs(p2.TotalCostUSD-2.0867235) > 1e-9 {
 		t.Errorf("Phase 2 cost = %f, want 2.0867235", p2.TotalCostUSD)
 	}
@@
-	if p4.TotalCostUSD != 0.17865225 {
+	if math.Abs(p4.TotalCostUSD-0.17865225) > 1e-9 {
 		t.Errorf("Phase 4 cost = %f, want 0.17865225", p4.TotalCostUSD)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/scraper/gcs_test.go` around lines 97 - 99, The
test currently uses exact float equality for p1.TotalCostUSD (and other checks
at the same file) which is brittle; update the assertions to use an epsilon
comparison instead—compute math.Abs(p1.TotalCostUSD - expected) and fail the
test (using t.Errorf or t.Fatalf) if that difference is greater than a small
epsilon (e.g. 1e-6); apply the same change for the other exact float checks
referenced (lines comparing other phase costs) so all float assertions use the
tolerance-based pattern; locate the checks by searching for TotalCostUSD and the
exact numeric literals in gcs_test.go.
jira-agent-dashboard/internal/integration_test.go (1)

135-155: Add end-to-end coverage for /api/comments/summary.

The new comments page is driven by that endpoint, but this flow only exercises /api/comments/{issueID}. That leaves the main comments dashboard path without integration coverage.

🧪 Example addition
+	t.Run("GET /api/comments/summary", func(t *testing.T) {
+		resp, err := http.Get(ts.URL + "/api/comments/summary?from=2024-01-01&to=2024-12-31")
+		if err != nil {
+			t.Fatalf("failed to GET /api/comments/summary: %v", err)
+		}
+		defer resp.Body.Close()
+
+		if resp.StatusCode != http.StatusOK {
+			t.Errorf("expected status 200, got %d", resp.StatusCode)
+		}
+
+		var comments []api.CommentSummary
+		if err := json.NewDecoder(resp.Body).Decode(&comments); err != nil {
+			t.Fatalf("failed to decode comments summary response: %v", err)
+		}
+
+		if len(comments) == 0 {
+			t.Error("expected at least 1 comment summary, got 0")
+		}
+	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/integration_test.go` around lines 135 - 155,
Add an integration subtest alongside the existing "GET /api/comments/{issueID}"
case that exercises the /api/comments/summary endpoint: perform an HTTP GET
against ts.URL + "/api/comments/summary", check resp.StatusCode ==
http.StatusOK, decode the response into the appropriate slice type (e.g.,
[]api.CommentSummary) and assert expected length/fields (e.g., totals or
top-level counts) so the comments dashboard flow is covered; reuse the same test
harness variables (t.Run, ts.URL, http.Get, resp.Body.Close) and mirror the
error handling/decoding pattern used for api.CommentDetail to keep consistency.
jira-agent-dashboard/internal/api/handlers.go (2)

343-357: Update precedes existence check, causing silent no-op on invalid ID.

UpdateCommentClassification is called before verifying the comment exists. If the ID is invalid, the UPDATE affects zero rows silently, and only the subsequent fetch returns 404. Consider checking existence first or having UpdateCommentClassification return the affected row count.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/api/handlers.go` around lines 343 - 357, The
handler calls UpdateCommentClassification before confirming the comment exists,
so updates on an invalid id can silently do nothing; either verify existence
first by calling s.store.GetReviewCommentByID(id) and returning 404 if missing
before calling s.store.UpdateCommentClassification, or change
s.store.UpdateCommentClassification to return the number of affected rows (or an
error when 0 rows are affected) and check that result in the handler to return
404; update the handler to use GetReviewCommentByID or the new affected-row
result to ensure a proper 404 for invalid IDs.

82-95: N+1 query pattern may cause performance issues.

For each issue, three additional database queries are executed (comments, complexity, phases). With 100 issues, this results in 301 queries instead of potentially 4 with batch queries or JOINs.

Consider adding batch methods like GetReviewCommentsByIssueIDs(ids []int64) to reduce round trips, or document that this endpoint is intended for small date ranges only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/api/handlers.go` around lines 82 - 95, The loop
over issues in the handler causes N+1 DB queries because for each issue you call
s.store.GetReviewCommentsByIssueID, s.store.GetPRComplexityByIssueID, and
s.store.GetPhaseMetricsByIssueID; add batch store methods (e.g.
GetReviewCommentsByIssueIDs(ids []int64), GetPRComplexityByIssueIDs(ids
[]int64), GetPhaseMetricsByIssueIDs(ids []int64)) that return results keyed by
issue ID, then change the handler to collect all issue IDs from the issues
slice, call these batch methods once, build maps from issue ID to
comments/complexity/phases, and populate the result []IssueSummary from those
maps instead of per-issue DB calls.
jira-agent-dashboard/internal/scraper/complexity.go (1)

123-169: Duplicated regex and parsing logic for gocyclo and gocognit.

gocycloRe and gocognitRe are identical, and ParseGocycloOutput/ParseGocognitOutput have the same logic. Consider extracting a shared helper:

func parseAverageComplexity(output, toolName string) (float64, error)

However, keeping them separate is acceptable if you anticipate the output formats diverging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/scraper/complexity.go` around lines 123 - 169,
ParseGocycloOutput and ParseGocognitOutput duplicate the same regex and parsing
logic (gocycloRe, gocognitRe and both Parse* functions); refactor by extracting
a shared helper (suggested name parseAverageComplexity(output, toolName string)
(float64, error)) that takes the output and tool name, runs the TrimSpace check,
applies the regex, parses the float and formats errors, then have
ParseGocycloOutput and ParseGocognitOutput call this helper (or replace them
entirely) to remove duplicated regex and parsing code while preserving existing
error messages and behavior.
jira-agent-dashboard/cmd/scraper/main.go (1)

79-81: Redundant os.Exit(0) call.

When main() returns normally, the process exits with code 0. The explicit os.Exit(0) is unnecessary and can be removed.

♻️ Suggested fix
 	log.Printf("Scraper completed successfully (step=%s).", *step)
-	os.Exit(0)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/cmd/scraper/main.go` around lines 79 - 81, Remove the
redundant os.Exit(0) at the end of main(): keep the existing log.Printf("Scraper
completed successfully (step=%s).", *step) and let main() return normally
instead of calling os.Exit(0); this removes the unnecessary explicit process
exit while preserving the success log (look for the log.Printf call and the
os.Exit invocation in main()).
jira-agent-dashboard/internal/db/store.go (1)

21-40: Centralize the noise-comment policy.

The filter rules here are duplicated in jira-agent-dashboard/internal/scraper/github.go::IsNoiseComment. The next rule change will drift insert-time filtering from query-time filtering and skew counts/classification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/store.go` around lines 21 - 40, The SQL
comment filtering logic in commentFilterSQL is duplicated with IsNoiseComment in
scraper/github.go; extract the canonical noise-comment policy (bot author
allowlist and body patterns: bot suffixes, '-robot', known bots like 'cwbotbot',
slash-commands, "No actionable comments were generated", "Skipped: comment is
from another GitHub bot", "<!-- walkthrough_start -->", "skip review by
coderabbit.ai", trimming rules) into a single shared helper in this package
(e.g., a exported ValidateNoiseComment/NoiseCommentPatterns or
BuildCommentFilterSQL helper) and update both commentFilterSQL and
IsNoiseComment to reference that shared policy so query-time and insert-time
filtering use the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jira-agent-dashboard/cmd/dashboard/main.go`:
- Around line 25-32: The SQLite connection is opened with mode=rw which prevents
creating a new DB file on first boot; update the sql.Open call that creates conn
(sql.Open("sqlite3", dbPath+"?mode=rw")) to use mode=rwc so the file will be
created if missing, leaving the rest of the logic (defer conn.Close() and the
db.InitSchema(conn) bootstrap) unchanged.

In `@jira-agent-dashboard/deploy/dashboard-deployment.yaml`:
- Around line 134-136: The Service is exposing the upstream app port 8080
(port/name "http") which must be removed; update the Service spec in
dashboard-deployment.yaml to delete the port entry that declares port: 8080 /
targetPort: 8080 / name: http so only the oauth-proxy HTTPS port (4443) remains
exported, and verify liveness/readiness probes (the container's probe settings)
still reference the containerPort directly and do not rely on the removed
Service port.

In `@jira-agent-dashboard/deploy/scraper-cronjob.yaml`:
- Around line 49-52: The cronjob mounts the same persistentVolumeClaim named
"dashboard-db" (volumes -> persistentVolumeClaim -> claimName: dashboard-db)
which is declared as ReadWriteOnce in pvc.yaml and so cannot be attached to
multiple nodes; change the job to avoid using that RWO PVC concurrently: either
create and reference a separate PVC for the scrapers or change the PVC type to
ReadWriteMany (or use a non-clustered storage class) and update claimName
accordingly, or remove the volume mount from the cronjob and use
network/database access instead; update the cronjob's volumes/volumeMounts (and
the referenced claimName) to point to the new PVC or remove them, and ensure
jira-agent-dashboard/deploy/pvc.yaml is adjusted if switching to RWX.
- Around line 15-16: The current CronJob schedule change plus concurrencyPolicy:
Forbid does not prevent concurrent runs across different CronJobs (scraper-prow
vs scraper-github) and causes SQLite "database is locked" errors; replace the
fragile schedule-stagger approach with a cross-job mutex: add a lock acquisition
step (e.g., an initContainer or entrypoint wrapper used by both CronJobs that
obtains a global lock) before the main scraper runs, using either a Kubernetes
Lease (client-go leader election) or an atomic ConfigMap/Secret create-or-update
loop or a flock on a shared PVC; update both CronJob manifests to include the
same lock name and ensure the initContainer/entrypoint blocks until it holds the
lock, then proceeds to the main container, and keep or remove concurrencyPolicy
as desired (it only affects same-CronJob concurrency).

In `@jira-agent-dashboard/internal/api/handlers_test.go`:
- Around line 20-33: The test setup opens an in-memory SQLite DB but doesn't
limit DB pooling, causing multiple in-memory instances; after sql.Open and
before InitSchema (i.e., where conn is created in the setup function), call
conn.SetMaxOpenConns(1) to force a single connection so InitSchema(conn),
db.NewStore(conn) and handlers invoked by httptest.NewServer share the same
in-memory database.

In `@jira-agent-dashboard/internal/api/handlers.go`:
- Line 243: The CreatedAt field is inconsistent: in handleGetIssues it uses
issue.StartedAt but in handleGetIssueDetail it uses issue.MergedAt (likely
copy-paste). Update the CreatedAt assignment in handleGetIssueDetail to call
formatOptionalTime(issue.StartedAt) instead of
formatOptionalTime(issue.MergedAt) so both handlers consistently use StartedAt;
locate the CreatedAt line in handleGetIssueDetail and replace the referenced
field accordingly.

In `@jira-agent-dashboard/internal/db/schema.go`:
- Around line 87-90: The migration loop currently swallows every error from
db.Exec(m) (the migrations slice loop), which can hide partial failures; change
it to capture the returned error from db.Exec and only ignore the specific
"column already exists" / duplicate-column SQLite error but treat all other
errors as fatal — return or log the error and halt startup. Locate the loop
iterating over migrations and replace the blind db.Exec(m) call with error
handling that checks the error value (or its message) and only suppresses the
known benign SQLite error, otherwise propagate or exit with the error.

In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 459-472: The current InsertOrUpdatePRComplexity upsert overwrites
stored complexity deltas with zero values when callers like refreshGitHub() or
backfillPRStats() only supply lines/files stats; update the
InsertOrUpdatePRComplexity implementation so it does not clobber existing
complexity fields: either (A) change the ON CONFLICT DO UPDATE clause to only
set lines_added, lines_deleted and files_changed (leave
cyclomatic_complexity_delta and cognitive_complexity_delta untouched), or (B)
use SQL expressions such as cyclomatic_complexity_delta =
COALESCE(excluded.cyclomatic_complexity_delta,
pr_complexity.cyclomatic_complexity_delta) and cognitive_complexity_delta =
COALESCE(excluded.cognitive_complexity_delta,
pr_complexity.cognitive_complexity_delta) so existing deltas are preserved when
the incoming PRComplexity has zero/NULL values; modify the SQL in
InsertOrUpdatePRComplexity accordingly and ensure callers that actually compute
complexity write the deltas explicitly.

In `@jira-agent-dashboard/internal/scraper/gcs.go`:
- Around line 249-270: The current json.Unmarshal of jsonBuf into PhaseTokens
silently ignores errors, causing phase metrics to be lost; update the code
around json.Unmarshal (the block using PhaseTokens, json.Unmarshal, phaseNum,
phaseNames, and result.Phases) to detect and handle errors: when json.Unmarshal
fails, log a warning or error including the phaseNum and the JSON snippet (or
error text) so the failure is visible, and still append a fallback PhaseTokens
entry (with PhaseNumber set to phaseNum and PhaseName resolved via phaseNames or
the existing switch) to result.Phases so the phase is not dropped; ensure the
logged message includes the unmarshalling error and enough context to debug.
- Around line 60-66: NewHTTPGCSClient currently falls back to http.DefaultClient
when passed nil, which has no timeout; change NewHTTPGCSClient to create and use
a dedicated *http.Client with a sensible Timeout instead of http.DefaultClient
when client == nil (update the NewHTTPGCSClient function and the
HTTPGCSClient.Client field initialization), e.g. instantiate
&http.Client{Timeout: <reasonable duration>} so GCS requests cannot hang
indefinitely; ensure any callers (e.g., where nil is passed) continue to work
with the new default.

In `@jira-agent-dashboard/internal/scraper/github.go`:
- Around line 137-146: GetPRReviewComments currently calls fetchComments for
pulls/{n}/comments and issues/{n}/comments but omits the pulls/{n}/reviews
endpoint that contains review bodies; update GetPRReviewComments to also call
fetchComments (or a new fetchReviews helper) for
fmt.Sprintf("%s/repos/%s/%s/pulls/%d/reviews?per_page=100") and merge those
results into the returned []CommentInfo (deduplicate if necessary), ensuring any
errors from that fetch are wrapped like the existing error handling.

In `@jira-agent-dashboard/internal/scraper/orchestrator_test.go`:
- Around line 78-86: The in-memory SQLite is creating multiple connections so
later calls on the pooled *db.Store miss prior data; after creating the DB with
sql.Open("sqlite3", ":memory:") set the pool to a single connection by calling
sqlDB.SetMaxOpenConns(1) immediately after sql.Open (before db.InitSchema and
before returning db.NewStore), ensuring sqlDB is used consistently for
db.InitSchema and subsequent operations.

In `@jira-agent-dashboard/internal/scraper/orchestrator.go`:
- Around line 137-150: The code currently seeds startedAt and finishedAt with
time.Now(), causing missing Prow metadata to be stamped with scrape time; change
initialization to use the zero time (e.g., declare var startedAt, finishedAt
time.Time) and only set them when o.gcs.ReadBuildFile(...) successfully
unmarshals prowTimestamp and ts.Timestamp > 0, leaving the zero value otherwise;
update any downstream logic that consumes startedAt/finishedAt (e.g., date
filtering or insertion into job_runs) to handle zero time appropriately rather
than assuming a non-zero time.

In `@jira-agent-dashboard/Makefile`:
- Around line 8-12: The dashboard and scraper targets call "$(GO) build -o
$(BINARY_DIR)/..." but don't ensure $(BINARY_DIR) exists, causing failures on
fresh checkouts; update the Makefile so the dashboard and scraper targets create
the directory first (e.g., run "mkdir -p $(BINARY_DIR)" before the "$(GO) build
-o ..." invocation) or add a phony/implicit target for $(BINARY_DIR) and make
dashboard and scraper depend on it so the directory is created before building.

In `@jira-agent-dashboard/web/comments.html`:
- Around line 113-115: The page currently loads Chart.js from the CDN via the
script tag "https://cdn.jsdelivr.net/npm/chart.js", which breaks in
offline/restricted environments and causes comments.js to fail; fix by vendoring
a local Chart.js build (e.g., vendor/chart.min.js) into the repo, update the
HTML to reference that local path instead of the CDN URL, and ensure the
vendored file is committed and referenced similarly in the other pages that use
Chart (replace the same CDN script tag found in the other HTML pages). Ensure
comments.js (and any init code that references the global Chart symbol)
continues to work with the vendored build.

In `@jira-agent-dashboard/web/css/style.css`:
- Around line 90-93: The .nav-toggle hover rule (and other hover-only rules)
lack keyboard focus styles; add matching :focus and :focus-visible rules for
.nav-toggle and each interactive selector referenced (the hover-only blocks at
lines noted) so keyboard users see a visible indicator—update selectors like
.nav-toggle:hover to include .nav-toggle:focus, .nav-toggle:focus-visible (and
replicate for the other hover-only classes) and define an accessible
outline/box-shadow or background change consistent with the hover state to
ensure parity between hover and keyboard focus.

In `@jira-agent-dashboard/web/js/comments.js`:
- Around line 238-240: prLink is built by string-concatenating comment.pr_url
using escapeHTML which doesn't prevent unsafe URL schemes; validate and sanitize
comment.pr_url before using it: parse comment.pr_url with the URL constructor
(or a regex) and allow only expected schemes (e.g., "https:"); if validation
passes, create the anchor element via DOM APIs (document.createElement('a')),
set its href to the validated URL, set target and rel attributes, and set its
textContent to the sanitized display string (e.g.,
comment.pr_url.replace('https://github.com/', '')); otherwise leave prLink
empty. Ensure you update the code that uses prLink (the variable constructed
where escapeHTML is currently used) to accept this DOM-created anchor or its
outerHTML only when the URL validation succeeds.

In `@jira-agent-dashboard/web/js/issue-detail.js`:
- Around line 29-39: The current header.innerHTML injects untrusted fields
(issueData.jira_url, issueData.jira_key, issueData.pr_url, issueData.pr_number)
directly into HTML causing stored XSS; replace the template-string assignment
with DOM construction using document.createElement for h2, a, div.meta and span
elements, set link href attributes only after validating/normalizing the URL
(and using target="_blank" with rel="noopener noreferrer"), and assign
textContent for jira_key, PR label and other text values; keep
formatDuration(issueData.merge_duration) and formatCost(issueData.total_cost)
but render their results as textContent into spans rather than interpolating
into innerHTML.

In `@jira-agent-dashboard/web/js/issues.js`:
- Around line 8-16: loadIssues can resolve out-of-order and overwrite newer
results; add a request guard (e.g., a monotonically incremented requestId or
per-call AbortController) so only the latest response updates issuesData and
calls updateResultsCount()/renderIssuesTable(). Specifically, introduce a
module-scoped currentRequestId (or currentAbortController), increment/create it
at the start of loadIssues, attach the id/abort token to the fetchAPI call, and
in the success path only assign to issuesData and call
updateResultsCount()/renderIssuesTable() if the id matches (or the request
wasn't aborted); also make sure the catch path ignores abort errors and only
shows showError for real failures.

---

Minor comments:
In `@jira-agent-dashboard/internal/api/handlers.go`:
- Around line 59-60: The two fields AvgDurationMs and AvgMergeDuration are both
assigned from t.AvgDurationMs; update the assignment in the struct literal so
AvgMergeDuration uses the correct source (e.g., t.AvgMergeDuration) if it
represents a different metric, or add a clarifying comment next to the
AvgMergeDuration line indicating it is intentionally an alias of AvgDurationMs
for backward compatibility; locate the assignment using the symbols
AvgDurationMs, AvgMergeDuration and the variable t to make the change.

In `@jira-agent-dashboard/internal/db/models.go`:
- Around line 53-54: The inline comment for the Topic field is out of date;
update the comment on the Topic field in models.go to list the current allowed
topic values (including architecture_design, security, ci, approval, process,
unclassified, etc.) or—preferably—extract those allowed topic strings into a
shared constant set (e.g., a package-level slice or const block like Topic*
constants) and reference that constant from both backend and frontend; change
the Topic field comment to mention the shared constants (or canonical source) so
the doc won’t drift.

In `@jira-agent-dashboard/README.md`:
- Around line 230-231: The README’s Topic list is missing values declared in the
code; update the README documentation to include the `architecture_design` and
`security` topics that are present in the allowedTopics map in
internal/api/handlers.go. Open the README section that enumerates Topic values
and add those two entries with the same naming/casing used by allowedTopics so
the docs match the code (ensure any examples or table rows referencing topics
are updated accordingly).

In `@jira-agent-dashboard/web/css/style.css`:
- Around line 173-175: The collapsed nav sibling rule "nav.collapsed ~ main {
margin-left: 48px; }" causes a persistent 48px offset on small screens; add a
mobile-specific override (using an appropriate media query for your mobile
breakpoint) that resets the margin-left to 0 for "nav.collapsed ~ main" so
collapsed navigation does not shift content on phones, and apply the same
override to the duplicate collapsed-nav rules referenced around lines 909-918
(the same selector occurrences) to ensure consistent mobile behavior.

In `@jira-agent-dashboard/web/issue.html`:
- Around line 14-16: On the issue-detail page (issue.html) mark the Issues nav
item as active by changing the <li><a href="issues.html">Issues</a></li> entry
to include the active state—add class="active" to the <li> and
aria-current="page" to the <a> (e.g., <li class="active"><a href="issues.html"
aria-current="page">Issues</a></li>); if nav is rendered via a template, set the
active flag for the "Issues" item when rendering issue.html so the same
attributes are injected dynamically.

In `@jira-agent-dashboard/web/issues.html`:
- Around line 62-63: The loading/empty placeholder row under the tbody with
id="issues-tbody" uses colspan="12" but the table actually defines 13 columns;
update the loading/empty <tr><td ...> cell to use colspan="13" (the row
rendering the "Loading..." state) so it aligns correctly when the action column
is present.

In `@jira-agent-dashboard/web/js/issues.js`:
- Line 33: The empty-state row inserted via tbody.innerHTML currently uses
colspan="12" which misaligns the empty message because the table has 13 headers
(including the action column); update the HTML string in the assignment to
tbody.innerHTML (the line setting '<tr><td colspan="12"...') to use colspan="13"
so the placeholder spans all columns and aligns with the table headers.
- Around line 77-79: The click handler flips the computed currentDirection
immediately, so when sortColumn !== column you default currentDirection to 'asc'
and then flip it to 'desc' on first click; change the logic so a new column
starts sorting ascending: compute newDirection based on whether the clicked
column matches sortColumn — if it does, flip sortDirection (sortDirection ===
'asc' ? 'desc' : 'asc'), otherwise set newDirection to 'asc'; update the code
that sets currentDirection/newDirection (referencing sortColumn, sortDirection,
currentDirection, newDirection) accordingly.

---

Nitpick comments:
In `@jira-agent-dashboard/cmd/scraper/main.go`:
- Around line 79-81: Remove the redundant os.Exit(0) at the end of main(): keep
the existing log.Printf("Scraper completed successfully (step=%s).", *step) and
let main() return normally instead of calling os.Exit(0); this removes the
unnecessary explicit process exit while preserving the success log (look for the
log.Printf call and the os.Exit invocation in main()).

In `@jira-agent-dashboard/deploy/rbac.yaml`:
- Line 4: The ClusterRoleBinding currently uses a generic cluster-scoped name
"dashboard-auth-delegator"; change metadata.name to a globally unique name
(e.g., "jira-agent-dashboard-auth-delegator") to avoid collisions, and update
any references to that binding elsewhere in manifests; locate the
ClusterRoleBinding resource (metadata.name) in rbac.yaml and rename it
consistently across the deployment/helm templates or other YAMLs that refer to
"dashboard-auth-delegator".

In `@jira-agent-dashboard/internal/api/handlers.go`:
- Around line 343-357: The handler calls UpdateCommentClassification before
confirming the comment exists, so updates on an invalid id can silently do
nothing; either verify existence first by calling
s.store.GetReviewCommentByID(id) and returning 404 if missing before calling
s.store.UpdateCommentClassification, or change
s.store.UpdateCommentClassification to return the number of affected rows (or an
error when 0 rows are affected) and check that result in the handler to return
404; update the handler to use GetReviewCommentByID or the new affected-row
result to ensure a proper 404 for invalid IDs.
- Around line 82-95: The loop over issues in the handler causes N+1 DB queries
because for each issue you call s.store.GetReviewCommentsByIssueID,
s.store.GetPRComplexityByIssueID, and s.store.GetPhaseMetricsByIssueID; add
batch store methods (e.g. GetReviewCommentsByIssueIDs(ids []int64),
GetPRComplexityByIssueIDs(ids []int64), GetPhaseMetricsByIssueIDs(ids []int64))
that return results keyed by issue ID, then change the handler to collect all
issue IDs from the issues slice, call these batch methods once, build maps from
issue ID to comments/complexity/phases, and populate the result []IssueSummary
from those maps instead of per-issue DB calls.

In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 21-40: The SQL comment filtering logic in commentFilterSQL is
duplicated with IsNoiseComment in scraper/github.go; extract the canonical
noise-comment policy (bot author allowlist and body patterns: bot suffixes,
'-robot', known bots like 'cwbotbot', slash-commands, "No actionable comments
were generated", "Skipped: comment is from another GitHub bot", "<!--
walkthrough_start -->", "skip review by coderabbit.ai", trimming rules) into a
single shared helper in this package (e.g., a exported
ValidateNoiseComment/NoiseCommentPatterns or BuildCommentFilterSQL helper) and
update both commentFilterSQL and IsNoiseComment to reference that shared policy
so query-time and insert-time filtering use the same source of truth.

In `@jira-agent-dashboard/internal/integration_test.go`:
- Around line 135-155: Add an integration subtest alongside the existing "GET
/api/comments/{issueID}" case that exercises the /api/comments/summary endpoint:
perform an HTTP GET against ts.URL + "/api/comments/summary", check
resp.StatusCode == http.StatusOK, decode the response into the appropriate slice
type (e.g., []api.CommentSummary) and assert expected length/fields (e.g.,
totals or top-level counts) so the comments dashboard flow is covered; reuse the
same test harness variables (t.Run, ts.URL, http.Get, resp.Body.Close) and
mirror the error handling/decoding pattern used for api.CommentDetail to keep
consistency.

In `@jira-agent-dashboard/internal/scraper/complexity.go`:
- Around line 123-169: ParseGocycloOutput and ParseGocognitOutput duplicate the
same regex and parsing logic (gocycloRe, gocognitRe and both Parse* functions);
refactor by extracting a shared helper (suggested name
parseAverageComplexity(output, toolName string) (float64, error)) that takes the
output and tool name, runs the TrimSpace check, applies the regex, parses the
float and formats errors, then have ParseGocycloOutput and ParseGocognitOutput
call this helper (or replace them entirely) to remove duplicated regex and
parsing code while preserving existing error messages and behavior.

In `@jira-agent-dashboard/internal/scraper/gcs_test.go`:
- Around line 97-99: The test currently uses exact float equality for
p1.TotalCostUSD (and other checks at the same file) which is brittle; update the
assertions to use an epsilon comparison instead—compute math.Abs(p1.TotalCostUSD
- expected) and fail the test (using t.Errorf or t.Fatalf) if that difference is
greater than a small epsilon (e.g. 1e-6); apply the same change for the other
exact float checks referenced (lines comparing other phase costs) so all float
assertions use the tolerance-based pattern; locate the checks by searching for
TotalCostUSD and the exact numeric literals in gcs_test.go.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: db75b2b0-e6a9-4a0c-bd86-51494782abcd

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed3475 and 09cde1a.

⛔ Files ignored due to path filters (1)
  • jira-agent-dashboard/go.sum is excluded by !**/*.sum
📒 Files selected for processing (46)
  • jira-agent-dashboard/.gitignore
  • jira-agent-dashboard/Containerfile
  • jira-agent-dashboard/Makefile
  • jira-agent-dashboard/README.md
  • jira-agent-dashboard/cmd/dashboard/main.go
  • jira-agent-dashboard/cmd/scraper/main.go
  • jira-agent-dashboard/deploy/dashboard-deployment.yaml
  • jira-agent-dashboard/deploy/dashboard-route.yaml
  • jira-agent-dashboard/deploy/kustomization.yaml
  • jira-agent-dashboard/deploy/namespace.yaml
  • jira-agent-dashboard/deploy/networkpolicy.yaml
  • jira-agent-dashboard/deploy/pvc.yaml
  • jira-agent-dashboard/deploy/rbac.yaml
  • jira-agent-dashboard/deploy/scraper-cronjob.yaml
  • jira-agent-dashboard/deploy/secrets.yaml.example
  • jira-agent-dashboard/go.mod
  • jira-agent-dashboard/internal/api/handlers.go
  • jira-agent-dashboard/internal/api/handlers_test.go
  • jira-agent-dashboard/internal/api/responses.go
  • jira-agent-dashboard/internal/api/server.go
  • jira-agent-dashboard/internal/db/models.go
  • jira-agent-dashboard/internal/db/schema.go
  • jira-agent-dashboard/internal/db/schema_test.go
  • jira-agent-dashboard/internal/db/store.go
  • jira-agent-dashboard/internal/db/store_test.go
  • jira-agent-dashboard/internal/integration_test.go
  • jira-agent-dashboard/internal/scraper/complexity.go
  • jira-agent-dashboard/internal/scraper/complexity_test.go
  • jira-agent-dashboard/internal/scraper/gcs.go
  • jira-agent-dashboard/internal/scraper/gcs_test.go
  • jira-agent-dashboard/internal/scraper/github.go
  • jira-agent-dashboard/internal/scraper/github_test.go
  • jira-agent-dashboard/internal/scraper/githubapp.go
  • jira-agent-dashboard/internal/scraper/githubapp_test.go
  • jira-agent-dashboard/internal/scraper/orchestrator.go
  • jira-agent-dashboard/internal/scraper/orchestrator_test.go
  • jira-agent-dashboard/web/comments.html
  • jira-agent-dashboard/web/css/style.css
  • jira-agent-dashboard/web/index.html
  • jira-agent-dashboard/web/issue.html
  • jira-agent-dashboard/web/issues.html
  • jira-agent-dashboard/web/js/app.js
  • jira-agent-dashboard/web/js/comments.js
  • jira-agent-dashboard/web/js/issue-detail.js
  • jira-agent-dashboard/web/js/issues.js
  • jira-agent-dashboard/web/js/trends.js

Comment thread jira-agent-dashboard/cmd/dashboard/main.go Outdated
Comment on lines +134 to +136
- port: 8080
targetPort: 8080
name: http

@coderabbitai coderabbitai Bot Apr 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the deployment manifest
find . -name "dashboard-deployment.yaml" -o -name "dashboard*.yaml" | head -20

Repository: openshift/hypershift

Length of output: 170


🏁 Script executed:

# Also search for related manifests
find . -path "*/deploy/*" -type f | grep -E "\.(yaml|yml)$" | head -30

Repository: openshift/hypershift

Length of output: 443


🏁 Script executed:

cat -n ./jira-agent-dashboard/deploy/dashboard-deployment.yaml

Repository: openshift/hypershift

Length of output: 4890


🏁 Script executed:

cat -n ./jira-agent-dashboard/deploy/networkpolicy.yaml

Repository: openshift/hypershift

Length of output: 1012


🏁 Script executed:

cat -n ./jira-agent-dashboard/deploy/dashboard-route.yaml

Repository: openshift/hypershift

Length of output: 427


Remove the raw app port from the Service.

Port 8080 is the unauthenticated upstream behind oauth-proxy and should not be exposed on the Service. While the NetworkPolicy restricts ingress traffic to only port 4443, exposing unnecessary ports violates defense-in-depth principles. Health probes (lines 88-99) communicate directly with the pod container port and do not require the Service to publish 8080.

Suggested change
   ports:
     - port: 4443
       targetPort: 4443
       name: https
-    - port: 8080
-      targetPort: 8080
-      name: http
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- port: 8080
targetPort: 8080
name: http
ports:
- port: 4443
targetPort: 4443
name: https
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/deploy/dashboard-deployment.yaml` around lines 134 -
136, The Service is exposing the upstream app port 8080 (port/name "http") which
must be removed; update the Service spec in dashboard-deployment.yaml to delete
the port entry that declares port: 8080 / targetPort: 8080 / name: http so only
the oauth-proxy HTTPS port (4443) remains exported, and verify
liveness/readiness probes (the container's probe settings) still reference the
containerPort directly and do not rely on the removed Service port.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deployment already has readOnlyRootFilesystem: true, runAsNonRoot: true, allowPrivilegeEscalation: false, and capabilities.drop: ALL configured. The OAuth proxy sidecar handles authn/authz. No changes needed.


AI-assisted response via Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bryan-cox, noted — though to clarify, the container security context settings you mentioned (readOnlyRootFilesystem, runAsNonRoot, etc.) are orthogonal to the concern I raised, which was specifically about the Service exposing port 8080 to the cluster network. Those pod-level controls harden the container runtime, not the network surface.

That said, the dashboard-allow-ingress NetworkPolicy does effectively mitigate the risk: it restricts all pod-level ingress to port 4443 from the ingress namespace only, so port 8080 on the pod is unreachable even if the Service advertises it. Given that defense layer, I understand the decision to keep the Service port for operational convenience (e.g., internal tooling or future use). I'll defer to your judgment here.

Comment thread jira-agent-dashboard/deploy/scraper-cronjob.yaml
Comment thread jira-agent-dashboard/deploy/scraper-cronjob.yaml
Comment thread jira-agent-dashboard/internal/api/handlers_test.go
Comment thread jira-agent-dashboard/web/comments.html Outdated
Comment thread jira-agent-dashboard/web/css/style.css
Comment thread jira-agent-dashboard/web/js/comments.js Outdated
Comment thread jira-agent-dashboard/web/js/issue-detail.js Outdated
Comment thread jira-agent-dashboard/web/js/outcomes.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
jira-agent-dashboard/internal/db/store.go (1)

459-468: ⚠️ Potential issue | 🔴 Critical

Preserve analyzed complexity deltas on conflict.

This upsert still overwrites both analyzed deltas every time PR stats are refreshed. Any caller that only knows lines_* / files_changed will reset previously computed complexity back to 0, and the row will look unanalyzed again downstream.

Split stats-only vs analyzed upserts, or make the delta columns nullable/pointer-backed so the conflict branch can preserve existing values when deltas were not supplied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/store.go` around lines 459 - 468, The upsert
in InsertOrUpdatePRComplexity currently always overwrites
cyclomatic_complexity_delta and cognitive_complexity_delta, wiping previously
analyzed values; change the logic so stats-only updates do not clobber existing
deltas by either: (a) making PRComplexity's delta fields nullable/pointer-backed
and using COALESCE in the ON CONFLICT update to keep existing
pr_complexity.cyclomatic_complexity_delta /
pr_complexity.cognitive_complexity_delta when excluded values are NULL, or (b)
splitting into two code paths: one update that only upserts
lines_added/lines_deleted/files_changed and another that also updates deltas
when the incoming PRComplexity includes non-nil delta values; update
InsertOrUpdatePRComplexity and the PRComplexity struct accordingly so callers
that don't supply deltas won't reset analyzed values.
🧹 Nitpick comments (1)
jira-agent-dashboard/internal/db/store_test.go (1)

331-352: Assert the persisted confidence value in this regression test.

This test exercises the new confidence path but never verifies that the value round-tripped from storage. A regression in the feature added by this PR would still pass here.

🧪 Suggested assertion
 	if got[0].Topic != "style" {
 		t.Errorf("Topic = %q, want %q", got[0].Topic, "style")
 	}
+	if got[0].Confidence == nil || *got[0].Confidence != conf {
+		t.Errorf("Confidence = %v, want %v", got[0].Confidence, conf)
+	}
 	if !got[0].HumanOverride {
 		t.Error("HumanOverride = false, want true")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/store_test.go` around lines 331 - 352, The
test calls UpdateCommentClassification(id, "nitpick", "style", &conf, true) but
never asserts that the confidence value persisted; add an assertion after
fetching comments via GetReviewCommentsByIssueID that the returned comment's
Confidence (e.g., got[0].Confidence) matches the original conf (use exact
equality or a small delta if type is float64) so the round-trip of the
confidence field is verified; locate this in the same test around the
UpdateCommentClassification and got variable checks and add the check alongside
the Severity/Topic/HumanOverride assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jira-agent-dashboard/internal/db/store_test.go`:
- Around line 13-17: The in-memory SQLite DB is per connection causing
flakiness; pin the test DB to a single connection by calling
conn.SetMaxOpenConns(1) and conn.SetMaxIdleConns(1) after opening the sql.DB so
all queries share the same in-memory instance (you can also use the shared
in-memory DSN like "file::memory:?mode=memory&cache=shared" if you prefer);
update the setup in store_test.go (where conn is opened) to set these limits so
the test harness never opens a second connection.

In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 490-500: The current GetIssuesNeedingComplexity uses 0 as a
sentinel for "not analyzed" (pc.cyclomatic_complexity_delta = 0 AND
pc.cognitive_complexity_delta = 0) which incorrectly selects PRs that
legitimately have zero deltas; instead add an explicit analysis marker (e.g.,
pr_complexity.analyzed_at TIMESTAMP or pr_complexity.is_analyzed BOOLEAN) or
make the delta columns nullable and treat NULL as "not analyzed", then change
GetIssuesNeedingComplexity to check that marker (e.g., pc.is_analyzed = FALSE or
pc.analyzed_at IS NULL or pc.cyclomatic_complexity_delta IS NULL OR
pc.cognitive_complexity_delta IS NULL) and update the code that writes
pr_complexity (the creation/update logic for pr_complexity rows) to set the
marker when analysis completes so processed rows are not returned again.
- Around line 596-603: The SQL CASE in the subquery calculating topic_penalty
uses literal topic strings that don't match the canonical ReviewComment.Topic
enum (so some topics map to 0); update the WHEN branches in the COALESCE
subquery to use the exact enum values from the ReviewComment.Topic model (e.g.,
replace architecture_design/security/etc. with the model's canonical values such
as api_design, documentation, etc.), keeping the same weights, and ensure the
subquery in store.go (the SELECT SUM CASE over review_comments rc producing
topic_penalty) and any references to commentFilterSQL("rc.author","rc.body")
remain intact.

---

Duplicate comments:
In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 459-468: The upsert in InsertOrUpdatePRComplexity currently always
overwrites cyclomatic_complexity_delta and cognitive_complexity_delta, wiping
previously analyzed values; change the logic so stats-only updates do not
clobber existing deltas by either: (a) making PRComplexity's delta fields
nullable/pointer-backed and using COALESCE in the ON CONFLICT update to keep
existing pr_complexity.cyclomatic_complexity_delta /
pr_complexity.cognitive_complexity_delta when excluded values are NULL, or (b)
splitting into two code paths: one update that only upserts
lines_added/lines_deleted/files_changed and another that also updates deltas
when the incoming PRComplexity includes non-nil delta values; update
InsertOrUpdatePRComplexity and the PRComplexity struct accordingly so callers
that don't supply deltas won't reset analyzed values.

---

Nitpick comments:
In `@jira-agent-dashboard/internal/db/store_test.go`:
- Around line 331-352: The test calls UpdateCommentClassification(id, "nitpick",
"style", &conf, true) but never asserts that the confidence value persisted; add
an assertion after fetching comments via GetReviewCommentsByIssueID that the
returned comment's Confidence (e.g., got[0].Confidence) matches the original
conf (use exact equality or a small delta if type is float64) so the round-trip
of the confidence field is verified; locate this in the same test around the
UpdateCommentClassification and got variable checks and add the check alongside
the Severity/Topic/HumanOverride assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ae1e7f9-b3f2-44ec-9fbe-f16dd4e1de32

📥 Commits

Reviewing files that changed from the base of the PR and between 09cde1a and c9002a9.

📒 Files selected for processing (4)
  • jira-agent-dashboard/internal/api/handlers.go
  • jira-agent-dashboard/internal/api/responses.go
  • jira-agent-dashboard/internal/db/store.go
  • jira-agent-dashboard/internal/db/store_test.go
✅ Files skipped from review due to trivial changes (1)
  • jira-agent-dashboard/internal/api/responses.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • jira-agent-dashboard/internal/api/handlers.go

Comment thread jira-agent-dashboard/internal/db/store_test.go
Comment thread jira-agent-dashboard/internal/db/store.go
Comment thread jira-agent-dashboard/internal/db/store.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
jira-agent-dashboard/internal/db/store.go (1)

494-510: ⚠️ Potential issue | 🟠 Major

0 remains an unsafe sentinel for "not analyzed".

PRs can legitimately have both complexity deltas equal to 0 (e.g., a PR that only changes comments or strings). These rows will be returned and reprocessed on every run. Consider adding an explicit analyzed_at timestamp or is_analyzed boolean column, or making the delta columns nullable and querying IS NULL.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/store.go` around lines 494 - 510,
GetIssuesNeedingComplexity currently treats 0 as a sentinel for "not analyzed"
which is unsafe; modify the schema and query so analysis is tracked explicitly
(e.g., add pr_complexity.analyzed_at TIMESTAMP or pr_complexity.is_analyzed
BOOLEAN, or make cyclomatic_complexity_delta and cognitive_complexity_delta
NULLable) and change the query in GetIssuesNeedingComplexity to filter on the
new indicator (e.g., WHERE pr_complexity.is_analyzed = FALSE or WHERE
pr_complexity.analyzed_at IS NULL or WHERE
pr_complexity.cyclomatic_complexity_delta IS NULL OR
pr_complexity.cognitive_complexity_delta IS NULL) instead of comparing to 0 so
legitimately-zero deltas are not repeatedly reprocessed; update any code paths
that write to pr_complexity (the code that sets deltas) to set the new analyzed
flag/timestamp when analysis completes.
🧹 Nitpick comments (8)
jira-agent-dashboard/Makefile (3)

1-1: Optionally add "all" target to satisfy checkmake.

The static analysis tool expects a conventional all target. While not required (the first target build serves as the default), adding it follows GNU Make conventions.

📋 Proposed addition
-.PHONY: build test lint clean dashboard scraper image deploy
+.PHONY: all build test lint clean dashboard scraper image deploy
+
+all: build
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/Makefile` at line 1, Add a conventional "all" Make
target that depends on the existing "build" target to satisfy checkmake
conventions; update the .PHONY declaration to include "all" alongside the
current targets (build test lint clean dashboard scraper image deploy) so "make
all" invokes the default build path and the phony list remains accurate.

20-21: Consider more comprehensive linting tools.

go vet provides basic checks. For more thorough static analysis, consider adding golangci-lint, which aggregates multiple linters and is widely adopted in Go projects.

🔍 Proposed enhancement
 lint:
 	$(GO) vet ./...
+	`@command` -v golangci-lint >/dev/null 2>&1 && golangci-lint run ./... || echo "golangci-lint not installed, skipping"

Or create a separate target:

-.PHONY: all build test lint clean dashboard scraper image deploy
+.PHONY: all build test lint lint-advanced clean dashboard scraper image deploy

 lint:
 	$(GO) vet ./...

+lint-advanced:
+	golangci-lint run ./...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/Makefile` around lines 20 - 21, The current lint target
only runs "$(GO) vet ./..." (target name: lint) which is minimal; add or replace
it with a more comprehensive linter invocation by integrating golangci-lint:
update the "lint" target (or add a new "lint-ci" target) to run "golangci-lint
run ./..." (and optionally fall back to running "$(GO) vet ./..." if
golangci-lint is not installed), and document the new dependency in the Makefile
comments so CI and local devs know to install golangci-lint before running the
target.

26-27: Parameterize image name and tag for CI/CD flexibility.

The hardcoded jira-agent-dashboard:latest works for local development but limits flexibility in automated pipelines. Consider adding variables:

🔧 Proposed refactor
 GO := go
 BINARY_DIR := bin
+IMAGE_NAME ?= jira-agent-dashboard
+IMAGE_TAG ?= latest
+IMAGE_REGISTRY ?=

 build: dashboard scraper
 image:
-	podman build -t jira-agent-dashboard:latest -f Containerfile .
+	podman build -t $(if $(IMAGE_REGISTRY),$(IMAGE_REGISTRY)/)$(IMAGE_NAME):$(IMAGE_TAG) -f Containerfile .

This allows overriding from the command line or CI:

make image IMAGE_TAG=v1.2.3 IMAGE_REGISTRY=quay.io/myorg
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/Makefile` around lines 26 - 27, Replace the hardcoded
image tag in the Makefile target named "image" with parameterized variables so
CI or CLI can override registry/name/tag; add default variables like
IMAGE_REGISTRY (default empty), IMAGE_NAME (default jira-agent-dashboard) and
IMAGE_TAG (default latest) and use them to construct the full image reference
for the podman build command (e.g.,
$(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)), ensuring the "image" target uses
that composed variable.
jira-agent-dashboard/web/js/comments.js (2)

213-221: Consider escaping class attribute values.

pattern.severity and pattern.topic are used directly in class attributes without escaping. While these values come from the API and should be from a known set, a compromised database or API could inject malicious content. The risk is low but consider using escapeHTML for defense in depth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/comments.js` around lines 213 - 221, The template
injection risk is that pattern.severity and pattern.topic are interpolated
directly into row.innerHTML class attributes in the patternArray.forEach block;
update the code to sanitize/escape those values before inserting into the DOM
(e.g., use an escapeHTML helper or set className/textContent instead of
innerHTML), ensuring the values used in class attributes and displayed text are
escaped; locate the row.innerHTML assignment in the forEach and replace
interpolation with escapedPatternSeverity and escapedPatternTopic (or use
row.classList/add and createElement/textContent for the topic/severity cells)
before calling tbody.appendChild.

9-21: Consider adding a request ID guard to prevent stale data.

Similar to issues.js, rapid time-range changes can cause out-of-order responses to overwrite newer data. The issues.js file already implements this pattern with activeLoadId.

♻️ Suggested fix
 let allComments = []; // stored for filtering
 let filteredComments = []; // current filtered view
+let activeLoadId = 0;

 // Load comments data and render charts
 async function loadComments(from, to) {
+  const loadId = ++activeLoadId;
   try {
     allComments = await fetchAPI(`/api/comments/summary?from=${from}&to=${to}`);
+    if (loadId !== activeLoadId) return;

     renderSeverityChart(allComments);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/comments.js` around lines 9 - 21, The
loadComments function can be overwritten by out-of-order fetches; add a
request-id guard like issues.js: introduce a module-scoped counter (e.g.,
activeCommentsLoadId) that you increment at the start of loadComments, capture
its value in a local loadId, and after the await
fetchAPI(`/api/comments/summary?...`) but before mutating state (setting
allComments and calling renderSeverityChart, renderTopicChart,
renderPatternTable, populateAuthorFilter, applyCommentFilters), check that
loadId === activeCommentsLoadId and abort if not; ensure errors still call
showError only for the active load.
jira-agent-dashboard/web/js/issues.js (1)

60-62: Consider validating URL schemes before placing in href.

Similar to the fix in comments.js, issue.jira_url and issue.pr_url are placed directly into href attributes. While these originate from trusted sources (server-constructed Jira URLs, GitHub scraper), adding scheme validation (startsWith('https://')) would provide defense in depth.

♻️ Suggested fix
+    const jiraHref = issue.jira_url?.startsWith('https://') ? escapeHTML(issue.jira_url) : '#';
+    const prHref = issue.pr_url?.startsWith('https://') ? escapeHTML(issue.pr_url) : '#';
+
     row.innerHTML = `
-      <td><a href="${escapeHTML(issue.jira_url)}" target="_blank" onclick="event.stopPropagation()">${escapeHTML(issue.jira_key)}</a></td>
-      <td><a href="${escapeHTML(issue.pr_url)}" target="_blank" onclick="event.stopPropagation()">#${issue.pr_number}</a></td>
+      <td><a href="${jiraHref}" target="_blank" onclick="event.stopPropagation()">${escapeHTML(issue.jira_key)}</a></td>
+      <td><a href="${prHref}" target="_blank" onclick="event.stopPropagation()">#${issue.pr_number}</a></td>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/issues.js` around lines 60 - 62, Validate the URL
schemes for issue.jira_url and issue.pr_url before inserting them into the href
in the row.innerHTML block: check that each string startsWith('https://') (or
otherwise meets allowed schemes) and only use the original URL in the href if it
passes; if it fails, use a safe fallback (e.g., '#' or omit the href) while
still rendering the visible escaped text via escapeHTML(issue.jira_key) and
`#`+issue.pr_number to avoid injecting unsafe schemes. Update the logic
surrounding row.innerHTML generation (the code that references issue.jira_url
and issue.pr_url) to perform this validation and choose the safe href value
accordingly.
jira-agent-dashboard/internal/scraper/orchestrator.go (1)

330-334: Error message string matching is fragile but pragmatic.

Checking strings.Contains(err.Error(), "UNIQUE constraint failed") relies on SQLite's exact error text. While error code checking would be more robust, the current approach works and is a common pattern with go-sqlite3. Consider documenting this behavior or adding a comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/scraper/orchestrator.go` around lines 330 -
334, The current check using strings.Contains(err.Error(), "UNIQUE constraint
failed") (the branch around the comment insertion that logs "Warning: could not
insert comment %d") is fragile because it relies on SQLite's exact text; add an
inline comment above that conditional explaining why textual matching is used
(reference to go-sqlite3 behavior and lack of portable error codes), note the
fragility and suggest future improvement to switch to error code checking when
available, and keep the existing logic (the strings.Contains check and the
log.Printf with c.ID) unchanged to preserve current behavior.
jira-agent-dashboard/internal/api/handlers.go (1)

343-357: Consider checking comment existence before updating.

If the comment ID doesn't exist, UpdateCommentClassification will succeed (affecting 0 rows), and then GetReviewCommentByID returns 404. While not harmful, checking existence first would provide a clearer error. Alternatively, check sql.Result.RowsAffected() after the update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/api/handlers.go` around lines 343 - 357, The
handler currently calls s.store.UpdateCommentClassification(...) then fetches
the comment with s.store.GetReviewCommentByID(id), which yields a 404 if the
update affected no rows; fix by ensuring existence is validated: either have
UpdateCommentClassification return the sql.Result (or rowsAffected) and check
result.RowsAffected()==0 to return http.StatusNotFound before fetching, or call
s.store.GetReviewCommentByID(id) first to verify the comment exists and return
404 if missing, then call UpdateCommentClassification; update error handling in
the handler accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jira-agent-dashboard/internal/scraper/orchestrator.go`:
- Around line 364-381: The current flow overwrites diff stats with zeros when
GetPRComplexityByIssueID fails; change the logic in orchestrator.go so that if
GetPRComplexityByIssueID returns a not-found condition you construct the
db.PRComplexity using diff stats from the analysis result (e.g.,
result.LinesAdded, result.LinesDeleted, result.FilesChanged) instead of
existing.LinesAdded/Deleted/FilesChanged, and only treat non-not-found errors as
logged warnings and skipped; continue to call InsertOrUpdatePRComplexity with
the assembled PRComplexity (using existing values when Get succeeds, or
result-provided diff stats when the row is missing).

In `@jira-agent-dashboard/web/js/issue-detail.js`:
- Around line 232-249: The template inserts comment.author directly into
innerHTML via commentDiv.innerHTML, risking XSS; change it to use the existing
escapeHTML helper when rendering the author (i.e., replace ${comment.author ||
'Unknown'} with ${escapeHTML(comment.author || 'Unknown')}) so the author string
is safely escaped before being embedded; update the template building in the
function that creates commentDiv (referenced by commentDiv and the template
block using formatDate, escapeHTML, severitySelect, topicSelect) to ensure
author values are consistently escaped.

---

Duplicate comments:
In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 494-510: GetIssuesNeedingComplexity currently treats 0 as a
sentinel for "not analyzed" which is unsafe; modify the schema and query so
analysis is tracked explicitly (e.g., add pr_complexity.analyzed_at TIMESTAMP or
pr_complexity.is_analyzed BOOLEAN, or make cyclomatic_complexity_delta and
cognitive_complexity_delta NULLable) and change the query in
GetIssuesNeedingComplexity to filter on the new indicator (e.g., WHERE
pr_complexity.is_analyzed = FALSE or WHERE pr_complexity.analyzed_at IS NULL or
WHERE pr_complexity.cyclomatic_complexity_delta IS NULL OR
pr_complexity.cognitive_complexity_delta IS NULL) instead of comparing to 0 so
legitimately-zero deltas are not repeatedly reprocessed; update any code paths
that write to pr_complexity (the code that sets deltas) to set the new analyzed
flag/timestamp when analysis completes.

---

Nitpick comments:
In `@jira-agent-dashboard/internal/api/handlers.go`:
- Around line 343-357: The handler currently calls
s.store.UpdateCommentClassification(...) then fetches the comment with
s.store.GetReviewCommentByID(id), which yields a 404 if the update affected no
rows; fix by ensuring existence is validated: either have
UpdateCommentClassification return the sql.Result (or rowsAffected) and check
result.RowsAffected()==0 to return http.StatusNotFound before fetching, or call
s.store.GetReviewCommentByID(id) first to verify the comment exists and return
404 if missing, then call UpdateCommentClassification; update error handling in
the handler accordingly.

In `@jira-agent-dashboard/internal/scraper/orchestrator.go`:
- Around line 330-334: The current check using strings.Contains(err.Error(),
"UNIQUE constraint failed") (the branch around the comment insertion that logs
"Warning: could not insert comment %d") is fragile because it relies on SQLite's
exact text; add an inline comment above that conditional explaining why textual
matching is used (reference to go-sqlite3 behavior and lack of portable error
codes), note the fragility and suggest future improvement to switch to error
code checking when available, and keep the existing logic (the strings.Contains
check and the log.Printf with c.ID) unchanged to preserve current behavior.

In `@jira-agent-dashboard/Makefile`:
- Line 1: Add a conventional "all" Make target that depends on the existing
"build" target to satisfy checkmake conventions; update the .PHONY declaration
to include "all" alongside the current targets (build test lint clean dashboard
scraper image deploy) so "make all" invokes the default build path and the phony
list remains accurate.
- Around line 20-21: The current lint target only runs "$(GO) vet ./..." (target
name: lint) which is minimal; add or replace it with a more comprehensive linter
invocation by integrating golangci-lint: update the "lint" target (or add a new
"lint-ci" target) to run "golangci-lint run ./..." (and optionally fall back to
running "$(GO) vet ./..." if golangci-lint is not installed), and document the
new dependency in the Makefile comments so CI and local devs know to install
golangci-lint before running the target.
- Around line 26-27: Replace the hardcoded image tag in the Makefile target
named "image" with parameterized variables so CI or CLI can override
registry/name/tag; add default variables like IMAGE_REGISTRY (default empty),
IMAGE_NAME (default jira-agent-dashboard) and IMAGE_TAG (default latest) and use
them to construct the full image reference for the podman build command (e.g.,
$(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)), ensuring the "image" target uses
that composed variable.

In `@jira-agent-dashboard/web/js/comments.js`:
- Around line 213-221: The template injection risk is that pattern.severity and
pattern.topic are interpolated directly into row.innerHTML class attributes in
the patternArray.forEach block; update the code to sanitize/escape those values
before inserting into the DOM (e.g., use an escapeHTML helper or set
className/textContent instead of innerHTML), ensuring the values used in class
attributes and displayed text are escaped; locate the row.innerHTML assignment
in the forEach and replace interpolation with escapedPatternSeverity and
escapedPatternTopic (or use row.classList/add and createElement/textContent for
the topic/severity cells) before calling tbody.appendChild.
- Around line 9-21: The loadComments function can be overwritten by out-of-order
fetches; add a request-id guard like issues.js: introduce a module-scoped
counter (e.g., activeCommentsLoadId) that you increment at the start of
loadComments, capture its value in a local loadId, and after the await
fetchAPI(`/api/comments/summary?...`) but before mutating state (setting
allComments and calling renderSeverityChart, renderTopicChart,
renderPatternTable, populateAuthorFilter, applyCommentFilters), check that
loadId === activeCommentsLoadId and abort if not; ensure errors still call
showError only for the active load.

In `@jira-agent-dashboard/web/js/issues.js`:
- Around line 60-62: Validate the URL schemes for issue.jira_url and
issue.pr_url before inserting them into the href in the row.innerHTML block:
check that each string startsWith('https://') (or otherwise meets allowed
schemes) and only use the original URL in the href if it passes; if it fails,
use a safe fallback (e.g., '#' or omit the href) while still rendering the
visible escaped text via escapeHTML(issue.jira_key) and `#`+issue.pr_number to
avoid injecting unsafe schemes. Update the logic surrounding row.innerHTML
generation (the code that references issue.jira_url and issue.pr_url) to perform
this validation and choose the safe href value accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: c307b5a7-094b-4abf-bf86-5c9e3bee330e

📥 Commits

Reviewing files that changed from the base of the PR and between c9002a9 and 7d6c5b4.

📒 Files selected for processing (11)
  • jira-agent-dashboard/Makefile
  • jira-agent-dashboard/cmd/dashboard/main.go
  • jira-agent-dashboard/internal/api/handlers.go
  • jira-agent-dashboard/internal/db/schema.go
  • jira-agent-dashboard/internal/db/store.go
  • jira-agent-dashboard/internal/scraper/gcs.go
  • jira-agent-dashboard/internal/scraper/orchestrator.go
  • jira-agent-dashboard/web/css/style.css
  • jira-agent-dashboard/web/js/comments.js
  • jira-agent-dashboard/web/js/issue-detail.js
  • jira-agent-dashboard/web/js/issues.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • jira-agent-dashboard/cmd/dashboard/main.go
  • jira-agent-dashboard/internal/db/schema.go
  • jira-agent-dashboard/web/css/style.css

Comment thread jira-agent-dashboard/internal/scraper/orchestrator.go Outdated
Comment thread jira-agent-dashboard/web/js/issue-detail.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
jira-agent-dashboard/internal/scraper/orchestrator.go (1)

330-334: Consider using errors.Is or a more robust error check for duplicate detection.

The check strings.Contains(err.Error(), "UNIQUE constraint failed") is fragile and depends on SQLite's exact error message format, which could change between versions.

For SQLite with github.com/mattn/go-sqlite3, you could check the error code:

💡 Suggested improvement
import sqlite3 "github.com/mattn/go-sqlite3"

// In the error handling:
var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) && sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
    // skip duplicate
    continue
}

Alternatively, the current approach is acceptable if you want to avoid the driver-specific dependency, but worth noting the brittleness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/scraper/orchestrator.go` around lines 330 -
334, The current duplicate-detection using strings.Contains(err.Error(), "UNIQUE
constraint failed") is brittle; replace it with a driver-aware error check by
importing the sqlite3 package (alias sqlite3) and using errors.As to cast err to
sqlite3.Error, then check sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique
(or sqliteErr.Code as appropriate) inside the error handling in the insert path
(around the block that logs "could not insert comment" for c.ID) and
skip/continue on that specific constraint error; keep a fallback to the existing
log for other errors.
jira-agent-dashboard/internal/db/schema.go (1)

9-14: Consider enabling foreign key enforcement.

SQLite does not enforce foreign key constraints by default. The schema declares REFERENCES clauses (e.g., issues.job_run_id REFERENCES job_runs(id)), but without PRAGMA foreign_keys=ON, these are purely documentary and won't prevent orphan rows or cascading issues.

If referential integrity is desired, add the pragma after WAL setup:

💡 Suggested change
 	_, err := db.Exec("PRAGMA journal_mode=WAL")
 	if err != nil {
 		return err
 	}
+	_, err = db.Exec("PRAGMA foreign_keys=ON")
+	if err != nil {
+		return err
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/internal/db/schema.go` around lines 9 - 14, The
InitSchema function currently enables WAL but does not turn on SQLite foreign
key enforcement, so REFERENCES in the schema (e.g., issues.job_run_id REFERENCES
job_runs(id)) are not enforced; modify InitSchema to execute "PRAGMA
foreign_keys=ON" (immediately after the existing "PRAGMA journal_mode=WAL" call)
and handle/return any error from that Exec call so foreign key constraints are
enforced at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jira-agent-dashboard/internal/db/store.go`:
- Around line 482-495: The SELECT in Store.GetPRComplexityByIssueID omits the
complexity_analyzed column so PRComplexity.ComplexityAnalyzed is always false;
update the query to include complexity_analyzed in the SELECT list and add
&c.ComplexityAnalyzed to the row.Scan call in GetPRComplexityByIssueID so the
boolean field on PRComplexity is populated from the DB.

---

Nitpick comments:
In `@jira-agent-dashboard/internal/db/schema.go`:
- Around line 9-14: The InitSchema function currently enables WAL but does not
turn on SQLite foreign key enforcement, so REFERENCES in the schema (e.g.,
issues.job_run_id REFERENCES job_runs(id)) are not enforced; modify InitSchema
to execute "PRAGMA foreign_keys=ON" (immediately after the existing "PRAGMA
journal_mode=WAL" call) and handle/return any error from that Exec call so
foreign key constraints are enforced at runtime.

In `@jira-agent-dashboard/internal/scraper/orchestrator.go`:
- Around line 330-334: The current duplicate-detection using
strings.Contains(err.Error(), "UNIQUE constraint failed") is brittle; replace it
with a driver-aware error check by importing the sqlite3 package (alias sqlite3)
and using errors.As to cast err to sqlite3.Error, then check
sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique (or sqliteErr.Code as
appropriate) inside the error handling in the insert path (around the block that
logs "could not insert comment" for c.ID) and skip/continue on that specific
constraint error; keep a fallback to the existing log for other errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 80e78668-76c4-4d92-b8a6-89a4165e3f30

📥 Commits

Reviewing files that changed from the base of the PR and between 7d6c5b4 and 948f4cb.

📒 Files selected for processing (6)
  • jira-agent-dashboard/internal/db/models.go
  • jira-agent-dashboard/internal/db/schema.go
  • jira-agent-dashboard/internal/db/store.go
  • jira-agent-dashboard/internal/db/store_test.go
  • jira-agent-dashboard/internal/scraper/orchestrator.go
  • jira-agent-dashboard/web/js/issue-detail.js
✅ Files skipped from review due to trivial changes (2)
  • jira-agent-dashboard/internal/db/models.go
  • jira-agent-dashboard/internal/db/store_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • jira-agent-dashboard/web/js/issue-detail.js

Comment thread jira-agent-dashboard/internal/db/store.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
jira-agent-dashboard/web/js/comments.js (1)

215-223: Consider defense-in-depth for class attribute interpolation.

pattern.severity and pattern.topic are interpolated directly into class attributes. While the server enforces allowlists (per PR description), using DOM APIs would provide client-side defense against any future server-side bugs or data corruption.

♻️ Safer approach using DOM APIs
   patternArray.forEach(pattern => {
     const row = document.createElement('tr');
-    row.innerHTML = `
-      <td><span class="tag ${pattern.severity}">${pattern.severity.replace(/_/g, ' ')}</span></td>
-      <td><span class="tag ${pattern.topic}">${pattern.topic.replace(/_/g, ' ')}</span></td>
-      <td>${formatNumber(pattern.count)}</td>
-    `;
+    const severityCell = document.createElement('td');
+    const severitySpan = document.createElement('span');
+    severitySpan.className = 'tag ' + pattern.severity;
+    severitySpan.textContent = pattern.severity.replace(/_/g, ' ');
+    severityCell.appendChild(severitySpan);
+
+    const topicCell = document.createElement('td');
+    const topicSpan = document.createElement('span');
+    topicSpan.className = 'tag ' + pattern.topic;
+    topicSpan.textContent = pattern.topic.replace(/_/g, ' ');
+    topicCell.appendChild(topicSpan);
+
+    const countCell = document.createElement('td');
+    countCell.textContent = formatNumber(pattern.count);
+
+    row.appendChild(severityCell);
+    row.appendChild(topicCell);
+    row.appendChild(countCell);
     tbody.appendChild(row);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jira-agent-dashboard/web/js/comments.js` around lines 215 - 223, The code
directly interpolates pattern.severity and pattern.topic into row.innerHTML
which risks class injection; instead build the cells and spans with DOM APIs:
create each td and span via document.createElement, set span.classList.add(...)
after sanitizing or mapping the severity/topic tokens (use
pattern.severity.replace(/_/g,' ') for display but a safe token for class
names), assign text via textContent (e.g., for the display value and
formatNumber(pattern.count)), and append the elements to row before
tbody.appendChild(row); update the block that iterates patternArray (the row
creation logic referencing pattern.severity, pattern.topic, tbody, formatNumber)
to use this safer DOM-construction approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jira-agent-dashboard/web/js/comments.js`:
- Around line 45-51: The filter callback used to produce `filtered` can throw if
`c.body` or `c.author` is null/undefined when `search` is set; update the
`allComments.filter` predicate to defensively handle missing strings by treating
`c.body` and `c.author` as empty strings (or use optional chaining) before
calling `toLowerCase()` so the `search` checks never call methods on null.
Locate the anonymous filter function (used to compute `filtered`) and replace
the direct `c.body.toLowerCase()` and `c.author.toLowerCase()` uses with safe
equivalents that default to `''` when those properties are falsy, preserving the
existing severity/topic/author checks and overall logic.

---

Nitpick comments:
In `@jira-agent-dashboard/web/js/comments.js`:
- Around line 215-223: The code directly interpolates pattern.severity and
pattern.topic into row.innerHTML which risks class injection; instead build the
cells and spans with DOM APIs: create each td and span via
document.createElement, set span.classList.add(...) after sanitizing or mapping
the severity/topic tokens (use pattern.severity.replace(/_/g,' ') for display
but a safe token for class names), assign text via textContent (e.g., for the
display value and formatNumber(pattern.count)), and append the elements to row
before tbody.appendChild(row); update the block that iterates patternArray (the
row creation logic referencing pattern.severity, pattern.topic, tbody,
formatNumber) to use this safer DOM-construction approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 18a69b8c-c63b-478b-8ec9-42e6cc7fd869

📥 Commits

Reviewing files that changed from the base of the PR and between 948f4cb and 91d773b.

📒 Files selected for processing (3)
  • jira-agent-dashboard/web/comments.html
  • jira-agent-dashboard/web/css/style.css
  • jira-agent-dashboard/web/js/comments.js
✅ Files skipped from review due to trivial changes (1)
  • jira-agent-dashboard/web/comments.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • jira-agent-dashboard/web/css/style.css

Comment thread jira-agent-dashboard/web/js/reviews.js Outdated
@openshift-ci

openshift-ci Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

@bryan-cox: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/security 4031985 link true /test security

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@bryan-cox
bryan-cox force-pushed the CNTRLPLANE-3043 branch 4 times, most recently from 4e77eb7 to c1bf525 Compare June 5, 2026 14:12
@openshift-ci openshift-ci Bot added area/documentation Indicates the PR includes changes for documentation and removed do-not-merge/needs-area labels Jun 5, 2026
@github-actions
github-actions Bot temporarily deployed to docs-preview/pr-8033 June 5, 2026 18:26 Inactive
@github-actions
github-actions Bot temporarily deployed to docs-preview/pr-8033 June 5, 2026 18:48 Inactive
@bryan-cox
bryan-cox force-pushed the CNTRLPLANE-3043 branch 7 times, most recently from 4acb764 to ecdcd11 Compare July 11, 2026 17:39
@hypershift-jira-solve-ci

hypershift-jira-solve-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Now I have the full root cause. The PR adds two new markdown files under docs/content/ but the aggregated-docs.md committed in the PR doesn't reflect the output of make update (specifically make docs-aggregate). The CI runs make generate update which regenerates aggregated-docs.md to include the new docs, and the git-diff check catches that the committed version doesn't match.

Test Failure Analysis Complete

Job Information

  • Prow Job: verify / Verify (GitHub Actions reusable workflow verify-reusable.yaml)
  • Build ID: 29247078513 (Job ID: 86806466018)
  • PR: #8033CNTRLPLANE-3043: Add jira-agent performance dashboard
  • Branch: CNTRLPLANE-3043
  • Failed Step: Step 11 — git update-index --refresh (dirty-tree check)

Test Failure Analysis

Error

docs/content/reference/aggregated-docs.md: needs update
##[error]Process completed with exit code 1.

Summary

The verify job runs make generate update and then checks that the working tree is clean (no uncommitted changes). The PR adds two new documentation files (docs/content/how-to/ci/ai-assisted-ci-jobs/index.md and docs/content/how-to/ci/ai-assisted-ci-jobs/jira-agent-dashboard.md) but the committed docs/content/reference/aggregated-docs.md does not include content from these new files. When CI runs make updatemake docs-aggregate, the hack/tools/docs-aggregator/main.go tool walks all .md files under docs/content/, discovers the two new docs, and regenerates aggregated-docs.md with their content included. The resulting file differs from what was committed, causing the git update-index --refresh dirty-tree check to fail.

Root Cause

The docs/content/reference/aggregated-docs.md file is an auto-generated aggregate of all markdown documentation files under docs/content/. It is produced by the docs-aggregate Makefile target (part of the update target chain), which runs go run ./hack/tools/docs-aggregator/main.go. This tool:

  1. Recursively walks docs/content/, collecting all .md files (excluding itself and blog posts)
  2. Sorts files alphabetically for deterministic output
  3. Concatenates them with --- separators and ## Source: <path> headers
  4. Strips images and converts markdown links to plain text

The PR adds two new documentation files:

  • docs/content/how-to/ci/ai-assisted-ci-jobs/index.md
  • docs/content/how-to/ci/ai-assisted-ci-jobs/jira-agent-dashboard.md

The committed version of aggregated-docs.md was not regenerated after these files were added (or was generated from an older state of the branch). When CI runs make generate update, the aggregator discovers 311 documentation files (including the two new ones), produces an updated aggregated-docs.md, and the git dirty-tree check detects the difference.

Recommendations
  1. Run make update locally (or specifically make docs-aggregate) to regenerate docs/content/reference/aggregated-docs.md with the new documentation files included, then commit the updated file:

    make docs-aggregate
    git add docs/content/reference/aggregated-docs.md
    git commit -m "Regenerate aggregated-docs.md"
  2. Alternative: Run the full verify sequence locally before pushing to catch this class of issue early:

    make generate update
    git diff --exit-code
  3. No code or CI fix needed — the CI is working correctly. The PR simply needs to include the regenerated artifact.

Evidence
Evidence Detail
Failing step Step 11: git update-index --refresh — dirty-tree check after make generate update
Error output docs/content/reference/aggregated-docs.md: needs update
Generation log Successfully aggregated 311 documentation files to docs/content/reference/aggregated-docs.md (line 1169 in CI log)
New docs added by PR docs/content/how-to/ci/ai-assisted-ci-jobs/index.md, docs/content/how-to/ci/ai-assisted-ci-jobs/jira-agent-dashboard.md
Generation mechanism make updatemake docs-aggregatego run ./hack/tools/docs-aggregator/main.go
All prior steps passed make generate update, staticcheck, fmt, vet, verify-api-deps, verify-crd-schema, verify-docs-nav — all succeeded

bryan-cox and others added 10 commits July 23, 2026 09:00
Add Kubernetes deployment manifests (Deployment, Route, PVC, CronJob,
RBAC, NetworkPolicy), container build files, Go module definition,
and Makefile for the JIRA Agent Dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add database schema, models, and store with queries for issues,
job runs, review comments, session telemetry, and trend aggregation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the scraper orchestrator that coordinates multi-step data
collection, and the GCS scraper that reads Prow CI build logs
to extract JIRA issue keys and session metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add GitHub scrapers that enrich issues with PR metadata (state,
merge status, timestamps) and review comments with severity
classification. Includes GitHub App authentication support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add code complexity analysis (gocyclo, cognitive), autodl session
telemetry scraper, and OpenTelemetry event ingestion for enriching
issue data with cost, token, and duration metrics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add HTTP API handlers for issues, trends, review comments, and
session telemetry. Includes server setup, response helpers,
integration tests, and CLI entrypoints for dashboard and scraper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CSS stylesheet, shared JS utilities (date formatting, time range
selectors, Chart.js helpers), trend chart rendering, and glossary page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the main overview page with business impact hero, health and
cost summary cards, charts, and activity feed. Add outcomes page
with per-issue detail view and issue drill-down page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pages

Add failure analysis page with root cause breakdown and failure
patterns. Add review quality page with reviewer metrics and comment
severity analysis. Add internals page with scraper pipeline status
and session telemetry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bryan-cox and others added 8 commits July 23, 2026 10:17
Add multi-job GCS scraping to support the installer team's periodic
jira-agent job alongside the existing HyperShift job. Each job gets
its own HTTPGCSClient configured with the correct GCS prefix and step
path. A job_name column in job_runs attributes data to each team.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread job_name from job_runs through the API as a component field
and surface it on the outcomes page with a filterable dropdown and
color-coded badges. Legacy rows default to "hypershift".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add toggle chip buttons for filtering data by team component (e.g.
hypershift, installer) across all 5 dashboard pages. Components are
dynamically discovered from data, and user selection persists across
time-range changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…persistence

Dynamically inject time range and component filter controls via app.js
into a header-bar-anchor div, eliminating HTML duplication across all 5
pages. Persist time range and component selections to localStorage so
they survive page navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… menu

Replace time-range buttons with a select element and convert component
filter chips into a dropdown checkbox menu for a more compact header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add trt-origin and trt-sippy job configs to the GCS scraper and parse
the TRT build-log format which uses "Issue: TRT-2823 |" instead of
"Processing: OCPBUGS-79071".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert the status donut chart to a stacked bar chart grouped by
component and compute the impact trend client-side from issue data
instead of a separate API call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add min-width:0 and overflow-x:hidden on main to contain flex item
growth, and overflow-wrap:break-word on comment bodies so long
unbroken strings wrap instead of stretching the viewport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci

openshift-ci Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Stale PRs are closed after 21d of inactivity.

If this PR is still relevant, comment to refresh it or remove the stale label.
Mark the PR as fresh by commenting /remove-lifecycle stale.

If this PR is safe to close now please do so with /close.

/lifecycle stale

@openshift-ci openshift-ci Bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/documentation Indicates the PR includes changes for documentation do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants