Skip to content

Developer metrics - #42

Merged
ananthanarayanan431 merged 10 commits into
mainfrom
developer-metrics
Jun 26, 2026
Merged

Developer metrics#42
ananthanarayanan431 merged 10 commits into
mainfrom
developer-metrics

Conversation

@ananthanarayanan431

@ananthanarayanan431 ananthanarayanan431 commented Jun 25, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Migration / DB schema change
  • Refactor
  • Docs / config

Checklist

  • Code follows project style (make check passes for backend, npm run lint for frontend)
  • Tests added or updated (make test passes)
  • Migrations created if schema changed (make migration name=...)
  • .env.example updated if new env vars added
  • No secrets or API keys committed

How to test

Related issues

Summary by CodeRabbit

  • New Features

    • Added an admin “Developer Metrics” dashboard with HTTP, bridge, optimization, and Sentry-backed issue/release insights.
    • Added an admin drawer to inspect Sentry issues and generate “AI Fix” guidance when available.
    • Updated the header to include an “API keys” link.
  • Bug Fixes

    • Improved prompt starring/un-starring so actions apply to the correct prompt version.
    • Enhanced domain reoptimization/deletion with clearer confirmations and toast error messages.
    • Improved streaming reliability for jobs and improved loading/error handling in the sidebar.
    • Clipboard copy now provides success/error toasts (with manual-copy fallback).

@supabase

supabase Bot commented Jun 25, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project xrktwhftlchlgujmmdwr because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ananthanarayanan-28, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 23 minutes and 32 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 503775ef-d993-4dc9-b23a-92d1329a031e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e1454e and ca048a8.

📒 Files selected for processing (18)
  • frontend/src/components/admin/analytics/issues-table.tsx
  • frontend/src/types/analytics.ts
  • frontend/src/types/api.ts
  • qa-chatbot/src/promptly/admin/api/router.py
  • qa-chatbot/src/promptly/admin/services/__init__.py
  • qa-chatbot/src/promptly/admin/services/analytics/__init__.py
  • qa-chatbot/src/promptly/admin/services/analytics/agent.py
  • qa-chatbot/src/promptly/admin/services/analytics/developer.py
  • qa-chatbot/src/promptly/admin/services/analytics/helpers.py
  • qa-chatbot/src/promptly/admin/services/analytics/platform.py
  • qa-chatbot/src/promptly/admin/services/audit.py
  • qa-chatbot/src/promptly/admin/services/health.py
  • qa-chatbot/src/promptly/admin/services/jobs.py
  • qa-chatbot/src/promptly/admin/services/openrouter.py
  • qa-chatbot/src/promptly/admin/services/sentry.py
  • qa-chatbot/src/promptly/admin/services/stats.py
  • qa-chatbot/src/promptly/admin/services/users.py
  • qa-chatbot/src/promptly/api/v1/prompts.py
📝 Walkthrough

Walkthrough

The PR adds developer analytics and request logging, updates prompt and optimize schemas and flows, and adjusts dashboard, domain, account, and bridge UI behavior.

Changes

Admin analytics and observability

Layer / File(s) Summary
Request log storage and middleware
qa-chatbot/src/promptly/models/api_request_log.py, qa-chatbot/src/promptly/migrations/versions/l6m7n8o9p0q1_add_api_request_logs.py, qa-chatbot/src/promptly/models/__init__.py, qa-chatbot/src/promptly/core/middleware.py, qa-chatbot/src/promptly/main.py
ApiRequestLog maps request metadata to api_request_logs, the migration creates the table and indexes, and the middleware writes request logs and is registered in the FastAPI app.
Sentry stats and analytics handler
qa-chatbot/src/promptly/config/app.py, qa-chatbot/src/promptly/admin/api/router.py, qa-chatbot/src/promptly/admin/api/schemas.py
AppSettings gains Sentry credentials, the admin router caches Sentry stats, and the analytics response schemas carry developer metrics payloads and Sentry/raw data.
Developer metrics dashboard
frontend/src/components/admin/analytics/developer-metrics.tsx, frontend/src/components/admin/analytics/issue-detail-panel.tsx, frontend/src/components/admin/analytics/issues-table.tsx, frontend/src/components/admin/analytics/releases-card.tsx, frontend/src/types/analytics.ts, frontend/src/components/admin/view-tab.tsx
The admin platform view adds developer_metrics, and the dashboard renders HTTP, Sentry, bridge, optimizer, and issue inspection sections.

Prompt library and optimize flows

Layer / File(s) Summary
Prompt library favorites and pagination
frontend/src/app/(dashboard)/prompt-library/[id]/page.tsx, frontend/src/hooks/use-favorites.ts, qa-chatbot/src/promptly/schemas/prompt.py, frontend/src/types/bridge.ts, frontend/src/types/api.ts
The unstar action passes a prompt version id to the unlike mutation, and the prompt-family list response gains pagination metadata.
Prompt accounting and validation
qa-chatbot/src/promptly/api/v1/prompts.py, qa-chatbot/src/promptly/optimize/api/schemas.py, frontend/src/lib/schemas.ts
Prompt usage logs now store token credits, job poll status is narrowed to fixed literals, and optimize prompt input no longer accepts an empty string.
Optimize job flow and copy feedback
frontend/src/components/optimize/optimize-chat.tsx, frontend/src/components/optimize/result-card.tsx, frontend/src/hooks/use-job-stream.ts
The optimize chat template mapping is centralized, clipboard copy errors surface a toast, and job streaming aborts stale requests and falls back to polling on stream timeout.

Account, domain, and bridge UI updates

Layer / File(s) Summary
Account chrome and loading states
frontend/src/components/layout/header.tsx, frontend/src/components/layout/sidebar.tsx, qa-chatbot/src/promptly/schemas/user.py
The header links to API keys, the sidebar shows token and session loading states, and UserResponse.token_balance no longer has a default value.
Domain workspace feedback
frontend/src/components/domain-prompts/domain-card.tsx, frontend/src/components/domain-prompts/domain-workspace.tsx
The domain card label changes to Q&A pairs, and the domain workspace adds toast errors plus inline delete confirmation.
Bridge transfer metadata
frontend/src/types/api.ts, frontend/src/types/bridge.ts, frontend/src/components/bridge/transfer-detail.tsx
Transfer job types are re-exported from the bridge types module, the summary type gains optional transfer metadata fields, and the transfer detail card changes the non-cached color.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A bunny hopped through logs and graphs,
Then tucked new metrics in soft scarves.
It twitched at prompts and clipboard sighs,
And blinked at Sentry-styled skies. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change, which adds a developer metrics dashboard and related analytics UI/backend support.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch developer-metrics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
qa-chatbot/src/promptly/api/v1/prompts.py (1)

75-83: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep advisory and health-score charges on the fixed 5-credit contract.

These two calls now persist credits_spent=token_count, which makes usage records variable even though both endpoints are priced at a fixed 5 credits. That will skew billing analytics and any reconciliation against token_balance whenever token_count !== 5. Align both deduct_tokens(...) and usage_repo.log(...) to the same fixed operation cost instead of the model token count.

Proposed fix
+PROMPT_REVIEW_COST = 5
+
 async def prompt_health_score(
@@
-    token_count = result.pop("_token_usage", 0)
-    if token_count:
-        await user_repo.deduct_tokens(current_user.user_id, token_count)
+    result.pop("_token_usage", 0)
+    await user_repo.deduct_tokens(current_user.user_id, PROMPT_REVIEW_COST)
@@
     usage_repo = UsageEventRepository(db)
     await usage_repo.log(
-        user_id=current_user.user_id, action="health_score", credits_spent=token_count
+        user_id=current_user.user_id, action="health_score", credits_spent=PROMPT_REVIEW_COST
     )
@@
-    token_count = result.pop("_token_usage", 0)
-    if token_count:
-        await user_repo.deduct_tokens(current_user.user_id, token_count)
+    result.pop("_token_usage", 0)
+    await user_repo.deduct_tokens(current_user.user_id, PROMPT_REVIEW_COST)
@@
     usage_repo = UsageEventRepository(db)
-    await usage_repo.log(user_id=current_user.user_id, action="advisory", credits_spent=token_count)
+    await usage_repo.log(
+        user_id=current_user.user_id,
+        action="advisory",
+        credits_spent=PROMPT_REVIEW_COST,
+    )

As per coding guidelines, "Each optimization costs 10 credits; health-score and advisory operations cost 5 credits each; return HTTP 402 when a user has insufficient credits".

Also applies to: 117-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/api/v1/prompts.py` around lines 75 - 83, The
health-score/advisory billing path is recording and deducting the model’s token
count instead of the fixed 5-credit contract. Update the logic in the prompt
handling flow around the user token deduction and UsageEventRepository.log call
so both use the same constant operation cost for health-score and advisory
actions. Make sure the relevant branches/functions in prompts.py (including the
code that calls deduct_tokens, log, and the advisory path it also applies to)
consistently charge and persist 5 credits, not token_count.

Source: Coding guidelines

frontend/src/types/bridge.ts (1)

58-71: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep transfer job API shapes in src/types/api.ts.

These added fields make TransferJobSummary an even closer mirror of the backend payload, but this file is still acting as the source of truth for that API shape. Please keep TransferJobSummary/TransferJobListResponse declared in src/types/api.ts and have bridge-specific code import them from there.

As per coding guidelines, frontend/src/{types,lib}/!(api).{ts,tsx}: "Define all TypeScript types mirroring backend response shapes in src/types/api.ts and Zod schemas for form validation in src/lib/schemas.ts."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/types/bridge.ts` around lines 58 - 71, The transfer job response
types are defined in the wrong place, so move TransferJobSummary and
TransferJobListResponse back to src/types/api.ts and keep that file as the
single source of truth for backend-shaped API types. Update bridge-specific code
in TransferJobSummary consumers to import these types from api.ts instead of
declaring or extending them in bridge.ts, and leave bridge.ts only for
bridge-specific types/helpers.

Source: Coding guidelines

frontend/src/components/layout/sidebar.tsx (1)

206-215: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don't reuse the loading placeholder for /users/me failures.

fetchedUser?.token_balance is undefined during the initial fetch and after a query error, so this leaves the token card in the shimmer state forever when /api/v1/users/me fails. Please thread isLoading/isError through the user query here and render a distinct error or hidden state instead of treating both cases as loading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/layout/sidebar.tsx` around lines 206 - 215, The user
query in the sidebar token card currently treats both initial loading and
`/api/v1/users/me` failures as the same `fetchedUser?.token_balance ===
undefined` state, which leaves the shimmer stuck on errors. Update the
`useQuery` usage in `sidebar.tsx` to read and pass through `isLoading` and
`isError` alongside `fetchedUser`, and make the token card render a real error
or hidden state when `useQuery<User>` fails instead of reusing the loading
placeholder.
🧹 Nitpick comments (1)
qa-chatbot/src/promptly/config/app.py (1)

26-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep Sentry settings in a dedicated config class.

These fields extend AppSettings instead of keeping observability config isolated by concern. Please move them into a Sentry/observability settings class and compose that where the analytics code reads them. As per coding guidelines, "Organize configuration by concern using separate settings classes in src/promptly/config/."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/config/app.py` around lines 26 - 28, The Sentry
fields are currently mixed into AppSettings instead of being isolated by
concern. Move SENTRY_AUTH_TOKEN, SENTRY_ORG_SLUG, and SENTRY_PROJECT_SLUG into a
dedicated Sentry/observability settings class under promptly/config, then
compose or inject that class wherever the analytics/Sentry setup reads these
values. Keep AppSettings focused on app-wide config and update any references to
use the new settings class name.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/admin/analytics/developer-metrics.tsx`:
- Around line 132-140: The threshold coloring logic in developer-metrics.tsx
treats placeholder values like “—” as failures because parseFloat() produces NaN
and the color falls through to red. Update the value-to-number handling in the
metrics color calculation so thresholds are only applied when the parsed value
is finite, and otherwise keep the default text color. Use the existing threshold
block around the color assignment in the DeveloperMetrics component to make this
check before applying the inverse/non-inverse comparisons.

In `@frontend/src/components/domain-prompts/domain-workspace.tsx`:
- Around line 1556-1568: The delete confirmation state in domain-workspace is
global, which lets a confirmation carry over to a different domain after the
selection changes. Update the delete flow around handleDelete and confirmDelete
so the confirmation is tied to the selected domain id (or reset whenever
selectedId changes), and ensure the delete affordance is only enabled/shown when
a domain is currently selected. Apply the same fix to the related delete UI
block referenced by the other occurrence in this component.

In `@frontend/src/hooks/use-job-stream.ts`:
- Around line 120-127: The missing-token branch in use-job-stream is vulnerable
to a stale update because getSession() is not checked against ctrl.signal before
setting failed state; after awaiting the session, verify the abort signal and
return early before calling setError or setStatus. Also replace the raw fetch
usage in use-job-stream with the shared Axios instance from src/lib/api.ts,
updating the HTTP calls in the stream setup and request path to use
axios.get/axios.post so token handling, 401 behavior, and abort support stay
centralized.

In `@qa-chatbot/src/promptly/admin/api/router.py`:
- Around line 191-205: The Sentry stats cache in _fetch_sentry_stats should be
keyed by days instead of using one global payload, since different ranges like
7/30/365 days can currently reuse the wrong data. Update the cache state and
lookup so each days value has its own entry, and apply the same fix to the
stale-on-error fallback used when building the daily chart so the zero-filled
result is also stored and reused per days. Use the existing _fetch_sentry_stats
helper and the downstream stats/chart path around the stale-error handling to
locate the changes.
- Around line 1763-1764: The daily-series window in the router logic is starting
from a timestamp with the current time-of-day, which can cause partial-day data
to be counted in totals but omitted from the per-day buckets. Update the
`cutoff` calculation in `router.py` to normalize the window start to the
beginning of the day before it is used by the later `DATE(...)` queries and
`_fill_days()`, so the aggregates and daily series stay aligned.
- Around line 226-256: The unresolved issue count is being derived from
`top_issues`, so it only reflects the first 5 displayed items instead of the
full unresolved set. Update the Sentry handling in the admin router method that
builds `result` to use a true total from the API response or a separate count
source, and do not base `unresolved_issue_count` on the sliced `raw_issues[:5]`
list. Keep `top_issues` limited for display, but compute the count independently
from that limit so it represents all unresolved issues.

In `@qa-chatbot/src/promptly/core/middleware.py`:
- Around line 27-43: The request log writer in _write_request_log currently
creates an unbounded async DB write per request, which can pile up under slow
database conditions. Update the middleware logging path to route writes through
a bounded queue or semaphore, and ensure saturated paths either drop logs or
batch them instead of spawning more tasks. Use the existing _write_request_log
and the middleware request logging flow as the main integration points.

---

Outside diff comments:
In `@frontend/src/components/layout/sidebar.tsx`:
- Around line 206-215: The user query in the sidebar token card currently treats
both initial loading and `/api/v1/users/me` failures as the same
`fetchedUser?.token_balance === undefined` state, which leaves the shimmer stuck
on errors. Update the `useQuery` usage in `sidebar.tsx` to read and pass through
`isLoading` and `isError` alongside `fetchedUser`, and make the token card
render a real error or hidden state when `useQuery<User>` fails instead of
reusing the loading placeholder.

In `@frontend/src/types/bridge.ts`:
- Around line 58-71: The transfer job response types are defined in the wrong
place, so move TransferJobSummary and TransferJobListResponse back to
src/types/api.ts and keep that file as the single source of truth for
backend-shaped API types. Update bridge-specific code in TransferJobSummary
consumers to import these types from api.ts instead of declaring or extending
them in bridge.ts, and leave bridge.ts only for bridge-specific types/helpers.

In `@qa-chatbot/src/promptly/api/v1/prompts.py`:
- Around line 75-83: The health-score/advisory billing path is recording and
deducting the model’s token count instead of the fixed 5-credit contract. Update
the logic in the prompt handling flow around the user token deduction and
UsageEventRepository.log call so both use the same constant operation cost for
health-score and advisory actions. Make sure the relevant branches/functions in
prompts.py (including the code that calls deduct_tokens, log, and the advisory
path it also applies to) consistently charge and persist 5 credits, not
token_count.

---

Nitpick comments:
In `@qa-chatbot/src/promptly/config/app.py`:
- Around line 26-28: The Sentry fields are currently mixed into AppSettings
instead of being isolated by concern. Move SENTRY_AUTH_TOKEN, SENTRY_ORG_SLUG,
and SENTRY_PROJECT_SLUG into a dedicated Sentry/observability settings class
under promptly/config, then compose or inject that class wherever the
analytics/Sentry setup reads these values. Keep AppSettings focused on app-wide
config and update any references to use the new settings class name.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a81bb491-098b-4d01-878b-4e6990142ec2

📥 Commits

Reviewing files that changed from the base of the PR and between a0edc2e and 3b9c43f.

📒 Files selected for processing (26)
  • frontend/src/app/(dashboard)/prompt-library/[id]/page.tsx
  • frontend/src/components/admin/analytics/developer-metrics.tsx
  • frontend/src/components/admin/view-tab.tsx
  • frontend/src/components/bridge/transfer-detail.tsx
  • frontend/src/components/domain-prompts/domain-card.tsx
  • frontend/src/components/domain-prompts/domain-workspace.tsx
  • frontend/src/components/layout/header.tsx
  • frontend/src/components/layout/sidebar.tsx
  • frontend/src/components/optimize/optimize-chat.tsx
  • frontend/src/components/optimize/result-card.tsx
  • frontend/src/hooks/use-favorites.ts
  • frontend/src/hooks/use-job-stream.ts
  • frontend/src/lib/schemas.ts
  • frontend/src/types/api.ts
  • frontend/src/types/bridge.ts
  • qa-chatbot/src/promptly/admin/api/router.py
  • qa-chatbot/src/promptly/api/v1/prompts.py
  • qa-chatbot/src/promptly/config/app.py
  • qa-chatbot/src/promptly/core/middleware.py
  • qa-chatbot/src/promptly/main.py
  • qa-chatbot/src/promptly/migrations/versions/l6m7n8o9p0q1_add_api_request_logs.py
  • qa-chatbot/src/promptly/models/__init__.py
  • qa-chatbot/src/promptly/models/api_request_log.py
  • qa-chatbot/src/promptly/optimize/api/schemas.py
  • qa-chatbot/src/promptly/schemas/prompt.py
  • qa-chatbot/src/promptly/schemas/user.py
💤 Files with no reviewable changes (2)
  • qa-chatbot/src/promptly/schemas/prompt.py
  • frontend/src/components/layout/header.tsx

Comment thread frontend/src/components/admin/analytics/developer-metrics.tsx
Comment thread frontend/src/components/domain-prompts/domain-workspace.tsx Outdated
Comment on lines 120 to +127
const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token ?? '';

if (!token) {
setError('Session expired — please refresh the page');
setStatus('failed');
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n frontend/src/hooks/use-job-stream.ts

Repository: ananthanarayanan431/promptly

Length of output: 8307


Correct the race condition in the missing-token branch and replace raw fetch with the Axios instance.

The getSession() call at line 120 is not bound to the abort signal. If the effect cancels while waiting for the session, the stale closure continues and overwrites the UI state with a "failed" error at lines 124‑125. Check ctrl.signal.aborted immediately after awaiting the session and before mutating state.

Additionally, the raw fetch calls at lines 39 and 130 violate the coding guideline requiring all frontend HTTP requests to use the centralized axios instance in src/lib/api.ts. This instance handles token injection, 401 redirects, and abort signaling consistently.

Proposed fix for race condition
       const { data: { session } } = await supabase.auth.getSession();
+      if (ctrl.signal.aborted) return;
       const token = session?.access_token ?? '';

       if (!token) {
+        if (ctrl.signal.aborted) return;
         setError('Session expired — please refresh the page');
         setStatus('failed');
         return;
       }

Refactor HTTP calls: Replace fetch with axios.get/post from src/lib/api.ts to comply with frontend network standards.

📝 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
const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token ?? '';
if (!token) {
setError('Session expired — please refresh the page');
setStatus('failed');
return;
}
const { data: { session } } = await supabase.auth.getSession();
if (ctrl.signal.aborted) return;
const token = session?.access_token ?? '';
if (!token) {
if (ctrl.signal.aborted) return;
setError('Session expired — please refresh the page');
setStatus('failed');
return;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/hooks/use-job-stream.ts` around lines 120 - 127, The
missing-token branch in use-job-stream is vulnerable to a stale update because
getSession() is not checked against ctrl.signal before setting failed state;
after awaiting the session, verify the abort signal and return early before
calling setError or setStatus. Also replace the raw fetch usage in
use-job-stream with the shared Axios instance from src/lib/api.ts, updating the
HTTP calls in the stream setup and request path to use axios.get/axios.post so
token handling, 401 behavior, and abort support stay centralized.

Comment thread qa-chatbot/src/promptly/admin/api/router.py Outdated
Comment thread qa-chatbot/src/promptly/admin/api/router.py Outdated
Comment thread qa-chatbot/src/promptly/admin/api/router.py Outdated
Comment thread qa-chatbot/src/promptly/core/middleware.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
qa-chatbot/src/promptly/api/v1/prompts.py (1)

75-80: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the same fixed credit cost for deduction and usage logging.

Lines 80 and 120 now persist credits_spent=5, but Lines 77 and 117 still deduct _token_usage. If _token_usage is not exactly 5, balances and usage metrics diverge. Use one 5-credit constant for the preflight check, deduction, and usage log.

As per coding guidelines, “Each optimization costs 10 credits; health-score and advisory operations cost 5 credits each; return HTTP 402 when a user has insufficient credits.” <coding_guidelines>

Suggested direction
+HEALTH_SCORE_CREDIT_COST = 5
+ADVISORY_CREDIT_COST = 5
+
@@
-    token_count = result.pop("_token_usage", 0)
-    if token_count:
-        await user_repo.deduct_tokens(current_user.user_id, token_count)
+    result.pop("_token_usage", None)
+    await user_repo.deduct_tokens(current_user.user_id, HEALTH_SCORE_CREDIT_COST)
 
     usage_repo = UsageEventRepository(db)
-    await usage_repo.log(user_id=current_user.user_id, action="health_score", credits_spent=5)
+    await usage_repo.log(
+        user_id=current_user.user_id,
+        action="health_score",
+        credits_spent=HEALTH_SCORE_CREDIT_COST,
+    )
@@
-    token_count = result.pop("_token_usage", 0)
-    if token_count:
-        await user_repo.deduct_tokens(current_user.user_id, token_count)
+    result.pop("_token_usage", None)
+    await user_repo.deduct_tokens(current_user.user_id, ADVISORY_CREDIT_COST)
 
     usage_repo = UsageEventRepository(db)
-    await usage_repo.log(user_id=current_user.user_id, action="advisory", credits_spent=5)
+    await usage_repo.log(
+        user_id=current_user.user_id,
+        action="advisory",
+        credits_spent=ADVISORY_CREDIT_COST,
+    )

Also applies to: 115-120

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/api/v1/prompts.py` around lines 75 - 80, The
health_score and advisory flows are deducting _token_usage while logging a fixed
credits_spent=5, which can desync balances and usage metrics. Update the
relevant prompt handlers in prompts.py, including the health_score path and the
advisory path, to use one shared 5-credit constant for the preflight credit
check, the user_repo.deduct_tokens call, and UsageEventRepository.log. Make sure
the same constant is used consistently in both branches so the deduction and
logged usage always match.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/admin/analytics/issues-table.tsx`:
- Around line 98-114: The selectable row in issues-table.tsx uses role="button"
but only handles Enter and has no visible focus treatment, so keyboard
interaction is incomplete. Update the row element used in the issues table to
either a real button or keep the current clickable div and add Space-key
activation alongside Enter in onKeyDown; also add a clear focus style for the
row so tab focus is visible when navigating issues in the admin analytics table.

In `@frontend/src/types/analytics.ts`:
- Around line 16-56: Move the backend-response contract types out of
analytics.ts and into api.ts: EndpointLatency, SentryIssue, SentryRelease, and
the raw shape on AnalyticsResponse should be defined in src/types/api.ts and
then imported back into analytics.ts. Update any references to these symbols so
analytics.ts only contains analytics-specific types, keeping all backend mirror
types in the shared API types module.

In `@qa-chatbot/src/promptly/admin/api/router.py`:
- Around line 3327-3437: The latest_event payload is unstable when the event
fetch fails because event_data stays as an empty object, which breaks consumers
that expect user, tags, breadcrumbs, and other fields. Update the latest event
handling in the issue detail response builder to either fail the endpoint when
the /events/latest/ call does not succeed, or always return a fully shaped
default latest_event object from the same code path that builds event_data, so
callers like issue-detail-panel.tsx can safely dereference it.
- Around line 3363-3372: The request info builder in router.py is returning raw
Sentry headers, which can expose secret-bearing values to the admin dashboard.
Update the request handling logic around the request_info construction to filter
headers through a small safe allowlist instead of slicing raw_headers, and keep
only non-sensitive headers such as content-type, accept, and user-agent before
returning them.
- Around line 3528-3568: The OpenRouter request in router.py forwards
issue_context from _build_ai_fix_prompt(payload) without sanitizing sensitive
incident data. Add a server-side redaction step before the httpx.AsyncClient
post call to strip stack-frame locals, request query strings, IPs, emails,
tokens, and other high-entropy values from the content sent in the messages
payload. Keep the prompt-building flow intact, but ensure only sanitized context
is included in the user message passed to openrouter.ai.
- Around line 3323-3324: The issue handling in the Sentry lookup currently turns
every non-200 response into a 404, which hides auth, rate-limit, and upstream
server failures. Update the response handling around issue_resp so it checks the
actual upstream status code: keep HTTPException with 404 only when Sentry
returns 404, and map other non-200 responses to an appropriate 502/503 instead.
Use the existing issue_resp.status_code branch and the HTTPException raise path
to preserve the right error semantics for callers.

---

Outside diff comments:
In `@qa-chatbot/src/promptly/api/v1/prompts.py`:
- Around line 75-80: The health_score and advisory flows are deducting
_token_usage while logging a fixed credits_spent=5, which can desync balances
and usage metrics. Update the relevant prompt handlers in prompts.py, including
the health_score path and the advisory path, to use one shared 5-credit constant
for the preflight credit check, the user_repo.deduct_tokens call, and
UsageEventRepository.log. Make sure the same constant is used consistently in
both branches so the deduction and logged usage always match.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f5b5fff-e49f-49ae-b977-3707826c7c57

📥 Commits

Reviewing files that changed from the base of the PR and between 3b9c43f and 7e1454e.

📒 Files selected for processing (13)
  • frontend/src/components/admin/analytics/developer-metrics.tsx
  • frontend/src/components/admin/analytics/issue-detail-panel.tsx
  • frontend/src/components/admin/analytics/issues-table.tsx
  • frontend/src/components/admin/analytics/releases-card.tsx
  • frontend/src/components/domain-prompts/domain-workspace.tsx
  • frontend/src/components/layout/sidebar.tsx
  • frontend/src/hooks/use-job-stream.ts
  • frontend/src/types/analytics.ts
  • qa-chatbot/src/promptly/admin/api/router.py
  • qa-chatbot/src/promptly/admin/api/schemas.py
  • qa-chatbot/src/promptly/api/v1/prompts.py
  • qa-chatbot/src/promptly/core/middleware.py
  • qa-chatbot/src/promptly/main.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/src/hooks/use-job-stream.ts
  • frontend/src/components/domain-prompts/domain-workspace.tsx
  • qa-chatbot/src/promptly/core/middleware.py
  • frontend/src/components/layout/sidebar.tsx
  • frontend/src/components/admin/analytics/developer-metrics.tsx

Comment thread frontend/src/components/admin/analytics/issues-table.tsx
Comment thread frontend/src/types/analytics.ts Outdated
Comment on lines +3323 to +3324
if issue_resp.status_code != 200:
raise HTTPException(status_code=404, detail="Issue not found in Sentry")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't collapse upstream Sentry failures into 404s.

Line 3323 maps every non-200 issue response to “not found”. Invalid credentials, rate limits, and Sentry 5xxs will all be misreported as 404s, which breaks operator triage and retry behavior. Return 404 only for an upstream 404, and translate other failures to 502/503.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/admin/api/router.py` around lines 3323 - 3324, The
issue handling in the Sentry lookup currently turns every non-200 response into
a 404, which hides auth, rate-limit, and upstream server failures. Update the
response handling around issue_resp so it checks the actual upstream status
code: keep HTTPException with 404 only when Sentry returns 404, and map other
non-200 responses to an appropriate 502/503 instead. Use the existing
issue_resp.status_code branch and the HTTPException raise path to preserve the
right error semantics for callers.

Comment on lines +3327 to +3437
event_data: dict[str, Any] = {}

if event_resp.status_code == 200:
event = event_resp.json()
exception_info: dict[str, Any] | None = None
request_info: dict[str, Any] | None = None
breadcrumbs: list[dict[str, Any]] = []

for entry in event.get("entries", []):
etype = entry.get("type", "")

if etype == "exception":
values = entry["data"].get("values", [])
if values:
exc = values[-1]
raw_frames = (exc.get("stacktrace") or {}).get("frames", [])
frames = [
{
"filename": f.get("filename", ""),
"lineno": f.get("lineno"),
"function": f.get("function", ""),
"context": f.get("context", []),
"in_app": bool(f.get("inApp", False)),
"vars": {k: str(v)[:120] for k, v in (f.get("vars") or {}).items()},
}
for f in raw_frames
]
exception_info = {
"exc_type": exc.get("type", ""),
"exc_value": str(exc.get("value", ""))[:500],
"mechanism": (exc.get("mechanism") or {}).get("type", ""),
"frames": frames[-20:],
}

elif etype == "request":
req = entry["data"]
raw_headers = req.get("headers") or []
exception_info_req_headers = (
raw_headers if isinstance(raw_headers, list) else list(raw_headers.items())
)
request_info = {
"method": req.get("method", ""),
"url": req.get("url", ""),
"query_string": req.get("query", "") or "",
"headers": exception_info_req_headers[:15],
}

elif etype == "breadcrumbs":
crumbs = (entry["data"].get("values") or [])[-12:]
breadcrumbs = [
{
"type": c.get("type", ""),
"category": c.get("category", ""),
"message": str(c.get("message") or "")[:120],
"level": c.get("level", ""),
"timestamp": c.get("timestamp", ""),
}
for c in crumbs
]

user = event.get("user") or {}
geo = user.get("geo") or {}
tags = event.get("tags") or []

event_data = {
"event_id": event.get("eventID", ""),
"timestamp": event.get("dateCreated", ""),
"user": {
"id": user.get("id"),
"email": user.get("email"),
"ip": user.get("ip_address"),
"geo_city": geo.get("city"),
"geo_country": geo.get("country_code"),
"geo_region": geo.get("region"),
},
"tags": [
{"key": str(t[0]), "value": str(t[1])}
if isinstance(t, list | tuple)
else {"key": str(t.get("key", "")), "value": str(t.get("value", ""))}
for t in tags
],
"exception": exception_info,
"request": request_info,
"breadcrumbs": breadcrumbs,
"release": (
event["release"].get("version")
if isinstance(event.get("release"), dict)
else str(event["release"])
if event.get("release") is not None
else None
),
}

return JSONResponse(
content={
"success": True,
"data": {
"issue": {
"id": str(issue.get("id", "")),
"short_id": issue.get("shortId", ""),
"title": issue.get("title", ""),
"level": issue.get("level", "error"),
"count": int(issue.get("count", 0) or 0),
"user_count": int(issue.get("userCount", 0) or 0),
"first_seen": issue.get("firstSeen", ""),
"last_seen": issue.get("lastSeen", ""),
"permalink": issue.get("permalink", ""),
"culprit": issue.get("culprit", ""),
"status": issue.get("status", ""),
},
"latest_event": event_data,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep latest_event shape stable when the event fetch fails.

Line 3327 initializes event_data to {}, and that empty object is returned unchanged whenever /events/latest/ is unavailable. frontend/src/components/admin/analytics/issue-detail-panel.tsx then dereferences data.latest_event.user, tags, and breadcrumbs unconditionally, so a transient Sentry event failure turns into a client-side crash. Either fail this endpoint when the latest event cannot be loaded, or return a fully shaped empty payload.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/admin/api/router.py` around lines 3327 - 3437, The
latest_event payload is unstable when the event fetch fails because event_data
stays as an empty object, which breaks consumers that expect user, tags,
breadcrumbs, and other fields. Update the latest event handling in the issue
detail response builder to either fail the endpoint when the /events/latest/
call does not succeed, or always return a fully shaped default latest_event
object from the same code path that builds event_data, so callers like
issue-detail-panel.tsx can safely dereference it.

Comment on lines +3363 to +3372
raw_headers = req.get("headers") or []
exception_info_req_headers = (
raw_headers if isinstance(raw_headers, list) else list(raw_headers.items())
)
request_info = {
"method": req.get("method", ""),
"url": req.get("url", ""),
"query_string": req.get("query", "") or "",
"headers": exception_info_req_headers[:15],
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Allowlist request headers before returning them to the dashboard.

These lines forward raw request headers from Sentry into the admin response. The frontend only strips cookie and authorization, so headers like x-api-key, cf-access-token, vendor session headers, or other custom secret-bearing headers will still be exposed to any admin who opens the issue. Prefer returning a small safe allowlist such as content-type, accept, and user-agent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/admin/api/router.py` around lines 3363 - 3372, The
request info builder in router.py is returning raw Sentry headers, which can
expose secret-bearing values to the admin dashboard. Update the request handling
logic around the request_info construction to filter headers through a small
safe allowlist instead of slicing raw_headers, and keep only non-sensitive
headers such as content-type, accept, and user-agent before returning them.

Comment on lines +3528 to +3568
issue_context = _build_ai_fix_prompt(payload)

system_prompt = (
"You are a senior backend engineer performing root-cause analysis on production errors "
"from a FastAPI / Python application. Be concise, specific, and actionable. "
"Always reference exact file paths and line numbers from the stack trace.\n\n"
"Respond in this exact markdown format:\n\n"
"## Root Cause\n"
"[2-3 sentences explaining *why* the error occurs]\n\n"
"## Location\n"
"`filename.py:line_number` in `function_name()`\n"
"[One sentence on what this code does and why it fails]\n\n"
"## Fix\n"
"```python\n"
"[Corrected code snippet, 5-15 lines]\n"
"```\n"
"[1-2 sentences explaining the change]\n\n"
"## Prevention\n"
"[One concrete tip to prevent this class of error recurring]"
)

async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {llm.OPENROUTER_API_KEY.get_secret_value()}",
"Content-Type": "application/json",
},
json={
"model": "openai/gpt-4.1-mini",
"max_tokens": 800,
"messages": [
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": (
"Analyze this production error and provide a fix:\n\n" + issue_context
),
},
],
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact incident context before sending it to OpenRouter.

issue_context can include stack-frame locals, full request URLs, and breadcrumb messages from production traffic, and Lines 3549-3568 forward that payload verbatim to a third-party model. That is enough to leak secrets, tokens, emails, or customer data during incident analysis. Strip locals, query strings, IPs/emails, and other high-entropy values server-side before the outbound call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa-chatbot/src/promptly/admin/api/router.py` around lines 3528 - 3568, The
OpenRouter request in router.py forwards issue_context from
_build_ai_fix_prompt(payload) without sanitizing sensitive incident data. Add a
server-side redaction step before the httpx.AsyncClient post call to strip
stack-frame locals, request query strings, IPs, emails, tokens, and other
high-entropy values from the content sent in the messages payload. Keep the
prompt-building flow intact, but ensure only sanitized context is included in
the user message passed to openrouter.ai.

@ananthanarayanan431
ananthanarayanan431 merged commit 2950d5a into main Jun 26, 2026
4 of 6 checks passed
@ananthanarayanan431
ananthanarayanan431 deleted the developer-metrics branch June 26, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants