Skip to content

Queries view#1145

Open
N2D4 wants to merge 12 commits intodevfrom
saved-queries
Open

Queries view#1145
N2D4 wants to merge 12 commits intodevfrom
saved-queries

Conversation

@N2D4
Copy link
Contributor

@N2D4 N2D4 commented Feb 2, 2026


Note

Medium Risk
Introduces new persisted analytics config surface area and changes default-merging/validation behavior (including disallowing dotted trustedDomains updates), plus a production data migration that updates config JSON in batches.

Overview
Adds a new Analytics Queries experience in the dashboard: users can run ClickHouse SQL, view results in a virtualized table with row-detail dialog, and create/save/update/delete queries organized into folders, persisted via environment config (analytics.queryFolders.*). The analytics navigation now includes a Queries item, and the command-palette RunQuery preview gains a “Save Query” flow that writes the same config and then routes to the queries page.

Extends the shared config schema/defaults to include analytics.queryFolders (environment-only, non-pushable) and adds comprehensive E2E tests plus schema-fuzzer coverage for creating/updating/deleting folders and nested queries.

Fixes config/migration behavior around domains.trustedDomains: auto-migrations now replaceAll schema sentinels, a new Prisma migration backfills missing parent keys for dotted trusted-domain overrides in EnvironmentConfigOverride, and tests now assert dotted trusted-domain updates are rejected. Also bumps low-level email send timeout from 10s to 15s and includes small agent/docs/tooling tweaks.

Written by Cursor Bugbot for commit ba88e99. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Analytics "Queries" page: create/organize folders, write/run SQL, save queries, view row details; "Queries" added to analytics navigation; shared row/table display utilities.
    • Analytics added to environment and org defaults (queryFolders).
  • Bug Fixes

    • DB migration to fix trusted-domains config format handling.
    • Email-send timeout increased to 15s.
    • Migration substitution now replaces all schema sentinels.
  • Documentation

    • Clarified build guidance and PR-review tips (use gh pr status; never auto-commit); added PR-review guide.
  • Tests

    • New end-to-end tests and schema-fuzzer updates for analytics config.

Copilot AI review requested due to automatic review settings February 2, 2026 03:37
@vercel
Copy link

vercel bot commented Feb 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-backend Ready Ready Preview, Comment Feb 14, 2026 2:16am
stack-dashboard Ready Ready Preview, Comment Feb 14, 2026 2:16am
stack-demo Ready Ready Preview, Comment Feb 14, 2026 2:16am
stack-docs Ready Ready Preview, Comment Feb 14, 2026 2:16am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

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

Adds a new Analytics "Queries" feature (client-side Queries page, SQL run/save flows, folder/query CRUD), shared analytics UI utilities, schema and fuzzer wiring, E2E tests, a DB migration for trustedDomains, small backend tweaks, and editorial docs guidance.

Changes

Cohort / File(s) Summary
Dashboard — Queries UI
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page.tsx, apps/dashboard/src/components/commands/run-query.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/shared.tsx, apps/dashboard/src/lib/apps-frontend.tsx
Adds a full Queries page with SQL editor, Run/Save flows, SaveQuery dialog, folder/query CRUD, shared row rendering utilities, virtualized table and row-detail UI, and navigation entry (./queries).
Backend scripts / ClickHouse flags
apps/backend/scripts/db-migrations.ts
Switched Prisma diff flags to use config datasource and imported ClickHouse admin client; removed unused env var usage.
DB migration — trustedDomains fix
apps/backend/prisma/migrations/.../migration.sql
Adds a batched SQL migration to insert missing parent keys for domains.trustedDomains.<id> paths into EnvironmentConfigOverride JSONB.
Config schema & fuzzer
packages/stack-shared/src/config/schema.ts, packages/stack-shared/src/config/schema-fuzzer.test.ts
Introduces analytics environment schema (queryFolders + nested queries), wires it into environmentConfigSchema, updates organization defaults, and adds fuzzer entries.
E2E tests
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts, apps/e2e/tests/backend/endpoints/api/v1/internal/config.test.ts
Adds extensive analytics-config E2E tests; updates trustedDomains tests to expect nested-object-only handling and error on mixed dot-notation.
Docs / Guidance
AGENTS.md, .cursor/commands/pr-comments-review.md
Editorial updates: guidance not to invoke build packages manually, gh pr status tip, and an explicit rule forbidding automatic git commits/staging by agents.
Misc / template / imports
packages/stack-shared/src/known-errors.tsx, packages/template/src/lib/.../admin-app-impl.ts
Small TypeScript directive change and import relocation/cleanup.
Email low-level timeout
apps/backend/src/lib/emails-low-level.tsx
Increased email-send-timeout from 10s to 15s and updated message text.
Auto-migrations substitution
apps/backend/src/auto-migrations/index.tsx
Replaced single replace with replaceAll for schema sentinel substitution in migration SQL.
Knowledge / tests docs
claude/CLAUDE-KNOWLEDGE.md, .cursor/commands/pr-comments-review.md
Added guidance on Prisma auto-migration timeouts and a PR-review helper doc.
E2E test additions
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Comprehensive tests covering create/update/delete and environment override behavior for analytics queryFolders/queries.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(90,144,255,0.5)
    participant User as User (Browser)
    end
    rect rgba(255,99,132,0.5)
    participant UI as Dashboard UI
    end
    rect rgba(54,162,235,0.5)
    participant AdminAPI as Admin API
    end
    rect rgba(75,192,192,0.5)
    participant ConfigStore as Config Store (env overrides)
    end
    rect rgba(153,102,255,0.5)
    participant DB as DB (ClickHouse / Postgres)
    end

    User->>UI: Open Queries page / Run or Save query
    UI->>AdminAPI: Read / Patch analytics.queryFolders (create/update/delete)
    AdminAPI->>ConfigStore: Read or apply environment-scoped override
    ConfigStore-->>AdminAPI: Return updated config
    UI->>DB: Execute SQL query
    DB-->>UI: Return query results
    AdminAPI-->>UI: Return success / updated config
    UI-->>User: Show results, folders, saved query link
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"I hopped through folders, queries, and tests,
Tucked SQL seeds into tidy nests.
With floppy ears and a cheeky grin,
I saved each query, neat within.
Hop on in — the analytics begin!" 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Failed to fetch base branch: From https://github.com/stack-auth/stack-auth
! [rejected] dev -> dev (non-fast-forward)
+ 4c6a89f...f2bf1d6 dev -> origin/dev (forced update)
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Queries view' is clear, concise, and accurately describes the main feature addition—a new analytics Queries page for managing saved SQL queries organized into folders.
Description check ✅ Passed The PR description is comprehensive and well-structured, providing risk assessment, feature overview, config changes, test coverage, and operational fixes. It goes significantly beyond the template requirements.
Docstring Coverage ✅ Passed Docstring coverage is 80.56% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch saved-queries
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch saved-queries
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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 and usage tips.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 2, 2026

Greptile Overview

Greptile Summary

Added a new Queries view page to the analytics section, allowing users to organize and manage saved SQL queries in folders. The implementation includes:

  • New queries page (apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/) with folder/query CRUD operations
  • Schema updates to support analytics.queryFolders in environment config (non-pushable)
  • Comprehensive E2E test coverage for query folder management
  • Navigation updates linking from tables page and command palette to the new queries page
  • Database migration script improvements using Prisma's --from-config-datasource flag

Key Changes

  • Query folders stored in environment config to prevent accidental pushes across branches
  • Virtualized table rendering for performance with large result sets
  • Proper use of runAsynchronouslyWithAlert for async operations throughout
  • Extensive test coverage with 636 lines of E2E tests

Issues Found

  • Critical bug in retry button (line 292 of page-client.tsx): onRetry() is invoked immediately instead of being passed as a callback reference

Confidence Score: 3/5

  • Safe to merge after fixing the retry button logic error
  • The PR is well-structured with comprehensive tests, but contains a critical logic error in the error retry handler that will cause the query to execute during render rather than on button click
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx requires fixing the retry button on line 292

Important Files Changed

Filename Overview
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx New queries view page with folder/query management. Has logic error in retry button (line 292).
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts Comprehensive test suite for analytics query folder CRUD operations and validation
packages/stack-shared/src/config/schema.ts Added analytics schema with query folders for environment config (non-pushable)

Sequence Diagram

sequenceDiagram
    participant User
    participant QueriesPage
    participant AdminApp
    participant ConfigAPI
    participant ClickHouse

    Note over User,ClickHouse: Creating and Running a Saved Query

    User->>QueriesPage: Click "New folder"
    QueriesPage->>ConfigAPI: PATCH /api/v1/internal/config/override/environment<br/>{analytics.queryFolders.{id}: {displayName, queries}}
    ConfigAPI-->>QueriesPage: Folder created
    
    User->>QueriesPage: Enter SQL query
    User->>QueriesPage: Click "Save"
    QueriesPage->>ConfigAPI: PATCH /api/v1/internal/config/override/environment<br/>{analytics.queryFolders.{folderId}.queries.{queryId}: {displayName, sqlQuery}}
    ConfigAPI-->>QueriesPage: Query saved

    User->>QueriesPage: Select saved query from folder
    QueriesPage->>QueriesPage: Load query SQL into editor
    QueriesPage->>AdminApp: queryAnalytics({query, timeout_ms})
    AdminApp->>ClickHouse: Execute SQL query
    ClickHouse-->>AdminApp: Return result rows
    AdminApp-->>QueriesPage: Display results in virtualized table
    
    User->>QueriesPage: Click row
    QueriesPage->>QueriesPage: Open row detail dialog

    Note over User,ClickHouse: Updating Saved Query

    User->>QueriesPage: Modify SQL in editor
    User->>QueriesPage: Click "Save" button
    QueriesPage->>ConfigAPI: PATCH /api/v1/internal/config/override/environment<br/>{analytics.queryFolders.{folderId}.queries.{queryId}: {displayName, sqlQuery}}
    ConfigAPI-->>QueriesPage: Query updated

    Note over User,ClickHouse: Deleting Query/Folder

    User->>QueriesPage: Click delete icon
    QueriesPage->>QueriesPage: Show confirmation dialog
    User->>QueriesPage: Confirm delete
    QueriesPage->>ConfigAPI: PATCH /api/v1/internal/config/override/environment<br/>{analytics.queryFolders.{id}: null}
    ConfigAPI-->>QueriesPage: Deleted
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a dedicated Analytics “Queries” view in the dashboard and introduces environment-level config storage for saved query folders/queries.

Changes:

  • Add analytics.queryFolders.* schema + defaults to environment config, plus new KnownErrors for missing folders/queries.
  • Add dashboard UI for running/saving queries (new /analytics/queries page) and link it into Analytics navigation.
  • Add schema fuzzing + new E2E coverage for analytics config override behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts Minor import reordering (type import placement).
packages/stack-shared/src/known-errors.tsx Add KnownErrors for missing query folders / saved queries.
packages/stack-shared/src/config/schema.ts Add analytics environment config schema + defaults for query folders/queries.
packages/stack-shared/src/config/schema-fuzzer.test.ts Extend schema fuzzer cases to include analytics config.
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts New E2E tests covering analytics config override flows.
apps/dashboard/src/lib/apps-frontend.tsx Add “Queries” navigation entry under Analytics.
apps/dashboard/src/components/commands/run-query.tsx Add “Save Query” affordance in query preview header.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx Replace “Query moved” dialog with link to new Queries page.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page.tsx Add server component wrapper for the new Queries route.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx Implement the Queries UI: folder list, save/delete, query runner + results table.
apps/backend/scripts/db-migrations.ts Update Prisma diff generation flags to use configured datasource/schema inputs.
AGENTS.md Add guidance note about waiting for background typechecking/linting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx:
- Around line 693-721: The delete handlers (handleDeleteFolder and
handleDeleteQuery) currently clear selected IDs and sqlQuery but leave result
state (hasQueried, rows, columns) intact; update both handlers so that when the
deleted item is the active selection you also reset results by calling
handleNewQuery() or explicitly setting hasQueried=false and clearing rows and
columns (in the same conditional that clears selectedFolderId/selectedQueryId
and sqlQuery) to prevent stale UI results; reference handleDeleteFolder,
handleDeleteQuery, handleNewQuery, and the state variables hasQueried, rows,
columns when making the change.
- Around line 1015-1016: The delete-icon elements use "opacity-0
group-hover:opacity-100" together with "transition-opacity", causing a
hover-enter fade; change those classNames (the elements with "p-1 rounded
opacity-0 group-hover:opacity-100 hover:bg-red-500/10 text-muted-foreground
hover:text-red-500 transition-opacity" and the similar occurrence) to remove the
opacity transition and instead use instantaneous hover-enter plus a color
transition on exit — e.g., replace "transition-opacity" with "transition-colors
hover:transition-none" (or simply add "hover:transition-none" and remove opacity
transition) so hover-enter is immediate while hover-exit still animates.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx:
- Around line 650-656: Update the sidebar Link text from singular to plural to
match the analytics nav and page label: change the Link component that currently
renders "Query" (the Link with href="/api/proxy?url=https%3A%2F%2Fgithub.com%2F.%2Fqueries" and className="flex items-center
gap-2 px-3 py-2 rounded-md text-sm text-muted-foreground ...") to render
"Queries" instead.

In `@apps/dashboard/src/components/commands/run-query.tsx`:
- Around line 457-470: The anchor uses a relative href ("./analytics/queries")
which can break projectId scoping; change it to an absolute path built with the
existing adminApp.projectId (e.g.
`/projects/${adminApp.projectId}/analytics/queries`) and replace the <a> with
Next.js client-side navigation by importing Link from next/link and using Link
around the Save Query content (keep SimpleTooltip and FloppyDiskIcon as-is).
Ensure you reference adminApp when constructing the URL so the link always
includes the correct projectId.

In `@apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts`:
- Around line 495-520: The test fails because updateConfig uses dot-notation
keys so IDs containing '.' are interpreted as path separators; replace the
dotted IDs with dot-free strings (e.g., change folderId and queryId to not
include '.') or instead pass the override as a nested object rather than a
dot-notation key (construct an object like { analytics: { queryFolders: {
[folderId]: { ... } } } } ) when calling updateConfig; update references to
folderId/queryId and the assertions that use getConfig accordingly so validation
passes.
🧹 Nitpick comments (4)
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts (1)

49-55: Prefer inline snapshots for config assertions.

Guidelines prefer toMatchInlineSnapshot for test assertions; this also makes diffs clearer when config shape changes.

Example refactor
-    expect(config.analytics.queryFolders[folderId]).toEqual({
-      displayName: "Test Folder",
-      sortOrder: 0,
-      queries: {},
-    });
+    expect(config.analytics.queryFolders[folderId]).toMatchInlineSnapshot(`
+      {
+        "displayName": "Test Folder",
+        "queries": {},
+        "sortOrder": 0,
+      }
+    `);
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx (3)

65-125: Consider extracting shared cell-formatting helpers.
isDateValue, parseClickHouseDate, JsonValue, and CellValue are duplicated across analytics tables and run-query previews; centralizing them in a shared helper/component would reduce drift.


403-407: Wrap dialog async actions with runAsynchronouslyWithAlert.
The dialog buttons pass async handlers directly; rejected promises can be unhandled and bypass alerting. Wrap the Create/Save/Delete actions with runAsynchronouslyWithAlert (example below shows Create).

🔧 Example adjustment
-          <Button onClick={handleCreate} disabled={!displayName.trim() || loading}>
+          <Button onClick={() => runAsynchronouslyWithAlert(handleCreate())} disabled={!displayName.trim() || loading}>
             {loading ? "Creating..." : "Create"}
           </Button>

As per coding guidelines, "Never try-catch-all, never void a promise, and never .catch(console.error) or similar. Use loading indicators instead when UI is involved. If async is necessary, use runAsynchronously or runAsynchronouslyWithAlert".

Also applies to: 496-500, 544-548


584-587: Use explicit error handling instead of silent fallbacks for missing config.

Replace the type assertion and silent fallbacks with ?? throwErr(...) to ensure config gaps are caught early. Import throwErr from @stackframe/stack-shared/dist/utils/errors and throw with a message explaining what's expected (e.g., "analytics.queryFolders not found in config").

- const analyticsConfig = (config as { analytics?: { queryFolders?: Record<string, ConfigFolder> } }).analytics ?? {};
- const queryFolders = analyticsConfig.queryFolders ?? {};
+ const analyticsConfig = config.analytics ?? throwErr("Missing analytics config");
+ const queryFolders = analyticsConfig.queryFolders ?? throwErr("Missing queryFolders in analytics config");

Alternatively, update the config type definition to properly reflect the expected structure.

- Fix onRetry() being invoked immediately instead of passed as reference
- Add sqlQuery.trim() to canSave check in SaveQueryDialog
- Clear results state when deleting the selected folder/query
- Add loading check to keyboard shortcut to prevent race condition
- Fix parseClickHouseDate for date-only strings (YYYY-MM-DD)
- Remove hover-enter fade on delete icons (use hover:transition-none)
- Rename sidebar link from "Query" to "Queries" for consistency
- Fix test IDs with dots that break dot-notation paths

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/dashboard/src/components/commands/run-query.tsx`:
- Around line 511-523: The handlers are invoking the async function immediately;
change them to pass the function reference so the wrapper can attach its error
handling before execution: in the onKeyDown Enter branch call
runAsynchronouslyWithAlert(handleCreateFolder) (not
runAsynchronouslyWithAlert(handleCreateFolder())), and update the Button onClick
to call runAsynchronouslyWithAlert(handleCreateFolder) (e.g., onClick={() =>
runAsynchronouslyWithAlert(handleCreateFolder)}) so runAsynchronouslyWithAlert
receives the function reference rather than the promise.
🧹 Nitpick comments (3)
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts (1)

49-55: Prefer inline snapshots for config object assertions.
Using toMatchInlineSnapshot() here makes expected shapes clearer and aligns with the snapshot serializer used across tests.

♻️ Suggested change
-    expect(config.analytics.queryFolders[folderId]).toEqual({
-      displayName: "Test Folder",
-      sortOrder: 0,
-      queries: {},
-    });
+    expect(config.analytics.queryFolders[folderId]).toMatchInlineSnapshot(`
+      {
+        "displayName": "Test Folder",
+        "queries": {},
+        "sortOrder": 0,
+      }
+    `);

As per coding guidelines, prefer .toMatchInlineSnapshot over other selectors when writing tests; use the snapshot serializer format.

apps/dashboard/src/components/commands/run-query.tsx (1)

396-402: Replace the config cast with a typed analytics config.
The config as { analytics?: ... } cast sidesteps type safety and can mask schema drift now that analytics.queryFolders is part of the shared config. Prefer updating the config typing (or adding a narrow runtime guard) and then access config.analytics?.queryFolders directly.
As per coding guidelines: "Never silently use fallback values when type errors occur. Update types or throw errors instead. Use ?? throwErr(...) over non-null assertions with clear error messages explaining the assumption".

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx (1)

589-592: Prefer typed analytics config over casting.
The cast bypasses type safety now that analytics.queryFolders is part of the schema. Please update config typings (or add a narrow runtime guard) and access config.analytics?.queryFolders directly.
As per coding guidelines: "Never silently use fallback values when type errors occur. Update types or throw errors instead. Use ?? throwErr(...) over non-null assertions with clear error messages explaining the assumption".

The rendered config applies defaults, so deleted items still show default
values. Check the environment override instead where null indicates deletion.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/dashboard/src/components/commands/run-query.tsx`:
- Around line 418-443: The handleSave async handler should surface errors via
runAsynchronouslyWithAlert: import runAsynchronouslyWithAlert from
"stackframe/stack-shared/dist/utils/promises" and either wrap the existing
handleSave function when passing it to the Button onClick (e.g., onClick={() =>
runAsynchronouslyWithAlert(handleSave)}) or refactor handleSave to call
runAsynchronouslyWithAlert internally so any thrown errors show an alert; keep
the loading state logic as-is and do not remove the existing finally block that
calls setLoading(false).
🧹 Nitpick comments (2)
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts (1)

61-65: Prefer inline snapshots for object assertions.

This test uses toEqual with a full object. The test guideline prefers inline snapshots to keep expectations aligned with the snapshot serializer; consider switching here and mirroring across similar assertions in this file.

♻️ Suggested change
-    expect(config.analytics.queryFolders[folderId]).toEqual({
-      displayName: "Test Folder",
-      sortOrder: 0,
-      queries: {},
-    });
+    expect(config.analytics.queryFolders[folderId]).toMatchInlineSnapshot(`
+      {
+        "displayName": "Test Folder",
+        "queries": {},
+        "sortOrder": 0,
+      }
+    `);

As per coding guidelines: **/*.test.{ts,tsx}: Prefer .toMatchInlineSnapshot over other selectors when writing tests. Check snapshot-serializer.ts to understand how snapshots are formatted.

apps/dashboard/src/components/commands/run-query.tsx (1)

647-651: Consider using runAsynchronously instead of .catch(() => {}).

While the comment explains that errors are handled inside runQuery, the empty catch pattern is discouraged. Using runAsynchronously would be cleaner and self-documenting.

♻️ Optional refactor
+import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
 ...
   const handleRetry = useCallback(() => {
-    runQuery().catch(() => {
-      // Error is already handled in runQuery
-    });
+    // Error is already handled in runQuery via setError
+    runAsynchronously(runQuery());
   }, [runQuery]);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/e2e/tests/backend/endpoints/api/v1/internal/config.test.ts (1)

553-576: ⚠️ Potential issue | 🟠 Major

Don’t silently accept dot‑notation trustedDomains overrides.
Returning 200 while ignoring the update creates a silent failure path for a security‑critical setting. This should be a validation error (400) with a clear message.

🛠️ Suggested test expectation (and backend behavior)
-    expect(dotNotationResponse.status).toBe(200);
+    expect(dotNotationResponse.status).toBe(400);
+    expect(dotNotationResponse.body).toContain("domains.trustedDomains");
🤖 Fix all issues with AI agents
In
`@apps/backend/prisma/migrations/20260202000000_fix_trusted_domains_config/migration.sql`:
- Around line 35-99: The CTEs reference EnvironmentConfigOverride without a
schema, which breaks when search_path isn't public; update every CTE
(rows_to_check, matching_keys, missing_parents, parents_to_add,
updated_with_keys, marked_as_checked) to qualify EnvironmentConfigOverride with
the schema sentinel (e.g., "public"."EnvironmentConfigOverride" or the
appropriate schema identifier) wherever that table is referenced so the
migration does not rely on search_path.
🧹 Nitpick comments (2)
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts (2)

59-65: Prefer inline snapshots for object-shape assertions.
This aligns with the test guideline and reduces repetitive object literals in these config assertions.


306-341: Cascade-delete test doesn’t actually assert query override cleanup.
The test name says queries are deleted, but only the folder key is checked.

✅ Add explicit checks for query key cleanup
   // Verify folder is deleted (check override since rendered config applies defaults)
   const override = await getEnvironmentOverride(adminAccessToken);
   expect(override["analytics.queryFolders.cascade-folder"]).toBeNull();
+  expect(override["analytics.queryFolders.cascade-folder.queries.query-1"]).toBeUndefined();
+  expect(override["analytics.queryFolders.cascade-folder.queries.query-2"]).toBeUndefined();

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/dashboard/src/components/commands/run-query.tsx`:
- Around line 395-396: The component currently calls
adminApp?.useProject().useConfig() which conditionally invokes hooks
(useProject/useConfig) via optional chaining; move the null-check for
useAdminAppIfExists() (i.e. if (!adminApp) return null;) to the very start of
the component (before any calls to adminApp?.useProject() or
adminApp?.useProject().useConfig()) so that hooks from useProject and useConfig
are only called unconditionally and in the same order every render.
🧹 Nitpick comments (1)
apps/dashboard/src/components/commands/run-query.tsx (1)

655-659: Replace empty .catch() with runAsynchronouslyWithAlert.

The empty .catch(() => {}) silently swallows any errors that escape runQuery's internal try-catch. Per coding guidelines, avoid .catch(console.error) or similar patterns.

Since runQuery already handles expected errors via setError(), use runAsynchronouslyWithAlert to surface any unexpected failures.

♻️ Suggested refactor
  const handleRetry = useCallback(() => {
-   runQuery().catch(() => {
-     // Error is already handled in runQuery
-   });
+   runAsynchronouslyWithAlert(runQuery);
  }, [runQuery]);

As per coding guidelines: "Never try-catch-all, never void a promise, and never .catch(console.error) or similar."

- Create shared.tsx with common types, utilities, and components
- Export: RowData, ConfigFolder, FolderWithId types
- Export: isDateValue, isJsonValue, parseClickHouseDate utilities
- Export: JsonValue, CellValue, RowDetailDialog, VirtualizedFlatTable, ErrorDisplay components
- Update queries/page-client.tsx to use shared imports
- Update tables/page-client.tsx to use shared imports (keeping local DateValue with context)
- Update run-query.tsx to use shared imports (keeping local DateValue with relative time)

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/commands/run-query.tsx (1)

446-450: ⚠️ Potential issue | 🟠 Major

Replace .catch suppression with runAsynchronouslyWithAlert.

Line 447 uses .catch(() => { ... }) to suppress promise errors, which violates the coding guideline. Since handleRetry is an interactive error-recovery callback in the UI (line 478), use runAsynchronouslyWithAlert(runQuery) instead to properly handle async execution with appropriate error alerts.

🔧 Suggested fix
-  const handleRetry = useCallback(() => {
-    runQuery().catch(() => {
-      // Error is already handled in runQuery
-    });
-  }, [runQuery]);
+  const handleRetry = useCallback(() => {
+    runAsynchronouslyWithAlert(runQuery);
+  }, [runQuery]);
🧹 Nitpick comments (1)
packages/stack-shared/src/known-errors.tsx (1)

133-136: Prefer @ts-expect-error (or fix typing) instead of @ts-ignore.

Line 134 now uses @ts-ignore-next-line, which can silently mask real type errors. Please restore @ts-expect-error or tighten the typing so the directive isn’t needed.

🔧 Suggested minimal change
-      // `@ts-ignore` legendary comment, may never be removed https://x.com/konstiwohlwend/status/1998543556567617780
+      // `@ts-expect-error` legendary comment, may never be removed https://x.com/konstiwohlwend/status/1998543556567617780
       super(...createFn(...args));

}
if (!isObjectLike(baseValue)) {
if (lastWasFunction && mergeValue == null) {
set(res, key, null);
Copy link

Choose a reason for hiding this comment

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

Dot notation config updates silently fail with 200 OK

Medium Severity

The applyDefaults function behavior change causes dot notation config updates for function-based defaults (like trustedDomains, oauth.providers, teams, permissions, etc.) to silently fail. The API returns 200 (success), but the changes are not applied. For example, 'domains.trustedDomains.2.baseUrl': 'https://example.com' returns 200 OK but results in an empty trustedDomains: {}. Users would believe their update succeeded when nothing changed. This is confirmed by the test change showing dot notation now produces {} instead of the expected entry.

Fix in Cursor Fix in Web


// Find the current query to get its display name and description
const folder = folders.find(f => f.id === selectedFolderId);
const currentQuery = folder?.queries.find(q => q.id === selectedQueryId);
Copy link

Choose a reason for hiding this comment

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

Missing optional chaining causes crash when folder not found

Medium Severity

The expression folder?.queries.find(...) has incorrect optional chaining placement. When folder is undefined, folder?.queries evaluates to undefined, then calling .find() on undefined throws a TypeError. The correct pattern is folder?.queries?.find(...) with the second optional chain, or an explicit guard check. This affects both handleUpdateCurrentQuery (line 446) and getDeleteDialogInfo (line 536), which can crash if the folder lookup fails due to a race condition or deleted folder.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/dashboard/src/components/commands/run-query.tsx`:
- Around line 189-208: Remove the temporary type assertion and defensive
throwErr usage in the folders useMemo: stop casting config to { analytics?: ...
} and instead access config.analytics and config.analytics.queryFolders directly
(remove the "Type assertion because..." comment and the "?? throwErr(...)"
fallbacks). Either regenerate the bundled types so CompleteConfig includes
analytics.queryFolders, or simply delete the cast and the throwErr calls in the
useMemo that builds FolderWithId (referencing folders, useMemo, analyticsConfig,
queryFolders, and throwErr) so the code relies on the proper non-optional
analytics types.
🧹 Nitpick comments (3)
apps/backend/prisma/migrations/20260202000000_fix_trusted_domains_config/migration.sql (1)

112-117: DROP INDEX should be schema-qualified for consistency.

The CREATE INDEX on Line 26 targets /* SCHEMA_NAME_SENTINEL */."EnvironmentConfigOverride", but the DROP INDEX on Line 116 uses an unqualified index name. While Postgres resolves index names via search_path, for consistency and to match the pattern used elsewhere in this migration, consider qualifying it.

Suggested fix
-DROP INDEX IF EXISTS "temp_eco_trusted_domains_checked_idx";
+DROP INDEX IF EXISTS /* SCHEMA_NAME_SENTINEL */."temp_eco_trusted_domains_checked_idx";
packages/stack-shared/src/config/schema.ts (1)

756-770: The lastWasFunction tracking logic is difficult to follow.

The comma-operator expression on Line 763 that sets lastWasFunction = true as a side effect inside a ternary is clever but impairs readability:

baseValue = has(baseValue, part) ? get(baseValue, part) : (typeof baseValue === 'function' ? (lastWasFunction = true, baseValue as any)(part) : undefined);

Consider splitting this into explicit if/else for clarity:

Suggested refactor
-        lastWasFunction = false;
-        baseValue = has(baseValue, part) ? get(baseValue, part) : (typeof baseValue === 'function' ? (lastWasFunction = true, baseValue as any)(part) : undefined);
+        if (has(baseValue, part)) {
+          lastWasFunction = false;
+          baseValue = get(baseValue, part);
+        } else if (typeof baseValue === 'function') {
+          lastWasFunction = true;
+          baseValue = (baseValue as any)(part);
+        } else {
+          lastWasFunction = false;
+          baseValue = undefined;
+        }
apps/dashboard/src/components/commands/run-query.tsx (1)

419-419: Unchecked as RowData[] cast on API response.

response.result is cast to RowData[] without validation. If the API returns an unexpected shape (e.g., not an array, or rows without string keys), downstream code will fail with confusing errors far from this line. Consider a runtime guard or at minimum an Array.isArray check.

Suggested minimal guard
-      const newRows = response.result as RowData[];
+      const newRows = response.result;
+      if (!Array.isArray(newRows)) {
+        throw new Error("Unexpected query result format: expected an array of rows");
+      }

As per coding guidelines: "Do NOT use as/any/type casts to bypass the type system unless you specifically asked the user about it. Most type casts are unnecessary." and "Code defensively… with good error messages explicitly stating assumptions."

Comment on lines +189 to +208
const folders = useMemo((): FolderWithId[] => {
// Type assertion because config types may not be updated yet
const analyticsConfig = (config as { analytics?: { queryFolders?: Record<string, ConfigFolder> } }).analytics
?? throwErr("Missing analytics config");
const queryFolders = analyticsConfig.queryFolders ?? throwErr("Missing queryFolders in analytics config");

return Object.entries(queryFolders)
.map(([id, folder]) => ({
id,
displayName: folder.displayName,
sortOrder: folder.sortOrder ?? 0,
queries: Object.entries(folder.queries).map(([queryId, q]) => ({
id: queryId,
displayName: q.displayName,
sqlQuery: q.sqlQuery,
description: q.description,
})),
}))
.sort((a, b) => a.sortOrder - b.sortOrder);
}, [config]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the file and get context around the type cast
cat -n apps/dashboard/src/components/commands/run-query.tsx | sed -n '185,210p'

Repository: stack-auth/stack-auth

Length of output: 1313


🏁 Script executed:

# Find ConfigFolder type definition
fd -e ts -e tsx | xargs rg -l "type ConfigFolder|interface ConfigFolder" | head -5

Repository: stack-auth/stack-auth

Length of output: 148


🏁 Script executed:

# Find the useConfig hook definition
rg -n "useConfig|useProject" apps/dashboard/src --type ts -g '*.ts' -g '*.tsx' | grep -E "(export.*useConfig|const useConfig)" | head -10

Repository: stack-auth/stack-auth

Length of output: 10405


🏁 Script executed:

# Check the config type in the SaveQueryDialog component
rg -B5 -A5 "analytics.*queryFolders\|queryFolders.*Record" apps/dashboard/src --type tsx | head -40

Repository: stack-auth/stack-auth

Length of output: 92


🏁 Script executed:

# Find CompleteConfig definition
rg -n "type CompleteConfig|export.*CompleteConfig" -A 20 --type ts | head -80

Repository: stack-auth/stack-auth

Length of output: 30389


🏁 Script executed:

# Find ConfigFolder definition
rg -n "type ConfigFolder|interface ConfigFolder" -A 10 --type ts

Repository: stack-auth/stack-auth

Length of output: 1235


🏁 Script executed:

# Search for analytics in config schema files
rg -n "analytics" --type ts -g "schema*" | head -20

Repository: stack-auth/stack-auth

Length of output: 289


🏁 Script executed:

# Find OrganizationRenderedConfig definition
rg -n "type OrganizationRenderedConfig|export.*OrganizationRenderedConfig" -A 30 packages/stack-shared/src/config/schema.ts | head -100

Repository: stack-auth/stack-auth

Length of output: 5525


🏁 Script executed:

# Find environmentAnalyticsSchema definition
rg -n "environmentAnalyticsSchema|analyticsSchema" -B 5 -A 30 packages/stack-shared/src/config/schema.ts | head -150

Repository: stack-auth/stack-auth

Length of output: 3094


🏁 Script executed:

# Check if queryFolders is in the analytics schema
rg -n "queryFolders" packages/stack-shared/src/config/schema.ts

Repository: stack-auth/stack-auth

Length of output: 137


🏁 Script executed:

# Find the analytics defaults
rg -n "analyticsDefaults|analytics.*defaults" -B 5 -A 15 packages/stack-shared/src/config/schema.ts | head -100

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Check the complete structure of the rendered config
rg -n "type OrganizationIncompleteConfig|environmentConfigDefaults" -B 5 -A 30 packages/stack-shared/src/config/schema.ts | grep -A 20 "environmentConfigDefaults"

Repository: stack-auth/stack-auth

Length of output: 4057


🏁 Script executed:

# Check if there's a way analytics could be missing from the config
rg -n "getConfig|useConfig" apps/dashboard/src/components/commands/run-query.tsx -B 2 -A 2

Repository: stack-auth/stack-auth

Length of output: 321


🏁 Script executed:

# Check what happens at runtime when config is used elsewhere
rg -n "config\.analytics" apps/dashboard/src --type tsx -g '*.tsx' | head -20

Repository: stack-auth/stack-auth

Length of output: 92


🏁 Script executed:

# Check what happens at runtime when config.analytics is used
rg -n "config\.analytics" apps/dashboard/src -A 2 | head -40

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# See if there are other similar type casts in the dashboard/analytics code
rg -n " as {" apps/dashboard/src --type ts | grep -i config

Repository: stack-auth/stack-auth

Length of output: 138


🏁 Script executed:

# See if there are other similar type casts in the dashboard/analytics code
rg -n "as {" apps/dashboard/src | head -20

Repository: stack-auth/stack-auth

Length of output: 136


🏁 Script executed:

# Check if analytics is always expected to exist in config
rg -n "analytics\?" packages/stack-shared/src/config/schema.ts | head -10

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Look at the actual schema definition more closely
sed -n '265,282p' packages/stack-shared/src/config/schema.ts

Repository: stack-auth/stack-auth

Length of output: 584


🏁 Script executed:

# Check if analytics is a required field in the organization config defaults
rg -n "organizationConfigDefaults" -A 170 packages/stack-shared/src/config/schema.ts | grep -E "(analytics|queryFolders)"

Repository: stack-auth/stack-auth

Length of output: 125


🏁 Script executed:

# See the complete organizationConfigDefaults structure
sed -n '535,702p' packages/stack-shared/src/config/schema.ts | tail -100

Repository: stack-auth/stack-auth

Length of output: 2637


Remove the unnecessary type cast — the config types should already include the analytics field.

The organizationConfigDefaults in the schema (line 690 onwards) already provides default values for analytics.queryFolders, which means CompleteConfig should have this field as required, not optional. The type cast at line 191 is a workaround for stale type definitions. Either:

  1. Remove the cast if types have been regenerated correctly, or
  2. Regenerate the bundled type definitions so that CompleteConfig properly includes the analytics field

The comment "Type assertion because config types may not be updated yet" suggests this was a temporary workaround. If the bundled types are current, the cast and defensive throwErr calls become unnecessary — they'll never actually execute.

🤖 Prompt for AI Agents
In `@apps/dashboard/src/components/commands/run-query.tsx` around lines 189 - 208,
Remove the temporary type assertion and defensive throwErr usage in the folders
useMemo: stop casting config to { analytics?: ... } and instead access
config.analytics and config.analytics.queryFolders directly (remove the "Type
assertion because..." comment and the "?? throwErr(...)" fallbacks). Either
regenerate the bundled types so CompleteConfig includes analytics.queryFolders,
or simply delete the cast and the throwErr calls in the useMemo that builds
FolderWithId (referencing folders, useMemo, analyticsConfig, queryFolders, and
throwErr) so the code relies on the proper non-optional analytics types.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

}

// Format a cell value for display
function CellValue({ value, truncate = true }: { value: unknown, truncate?: boolean }) {
Copy link

Choose a reason for hiding this comment

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

Unused CellValueWithRelativeDates lost relative date rendering

Medium Severity

CellValueWithRelativeDates and DateValue are defined but never used anywhere in the codebase. During the refactoring, VirtualizedFlatTable was moved to shared.tsx where it hardcodes the shared CellValue (which renders absolute dates via toLocaleString()). The command palette's query results previously displayed dates as relative timestamps (e.g., "5 minutes ago") using useFromNow, but this was lost because the renamed CellValueWithRelativeDates was never wired into the shared VirtualizedFlatTable. This is dead code that indicates an incomplete refactoring and a UX regression in the command palette.

Additional Locations (1)

Fix in Cursor Fix in Web

open,
onOpenChange,
}: {
row: RowData | null,
Copy link

Choose a reason for hiding this comment

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

Duplicate ErrorDisplay component in command palette file

Low Severity

A local ErrorDisplay in run-query.tsx is nearly identical to the one exported from shared.tsx. The only difference is that the local version calls onClick={onRetry} directly while the shared version wraps the callback in runAsynchronouslyWithAlert. The local one could be replaced by using the shared ErrorDisplay with runQuery as onRetry (since the shared version already wraps with runAsynchronouslyWithAlert), eliminating the local duplicate and the handleRetry wrapper.

Fix in Cursor Fix in Web

<TrashIcon className="h-3 w-3 text-red-500" />
</button>
);
}
Copy link

Choose a reason for hiding this comment

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

Unused DeleteIconButton component is dead code

Low Severity

DeleteIconButton is defined but never referenced anywhere in the codebase. The FolderItem component implements its own inline delete buttons instead. A grep confirms the only occurrence is at its definition.

Fix in Cursor Fix in Web

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.

1 participant