Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
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. Comment |
Greptile OverviewGreptile SummaryAdded a new Queries view page to the analytics section, allowing users to organize and manage saved SQL queries in folders. The implementation includes:
Key Changes
Issues Found
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
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
|
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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/queriespage) 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.
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Outdated
Show resolved
Hide resolved
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Outdated
Show resolved
Hide resolved
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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
toMatchInlineSnapshotfor 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, andCellValueare duplicated across analytics tables and run-query previews; centralizing them in a shared helper/component would reduce drift.
403-407: Wrap dialog async actions withrunAsynchronouslyWithAlert.
The dialog buttons pass async handlers directly; rejected promises can be unhandled and bypass alerting. Wrap the Create/Save/Delete actions withrunAsynchronouslyWithAlert(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. ImportthrowErrfrom@stackframe/stack-shared/dist/utils/errorsand 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.
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx
Show resolved
Hide resolved
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Outdated
Show resolved
Hide resolved
apps/e2e/tests/backend/endpoints/api/v1/analytics-config.test.ts
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
- 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>
There was a problem hiding this comment.
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.
UsingtoMatchInlineSnapshot()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.
Theconfig as { analytics?: ... }cast sidesteps type safety and can mask schema drift now thatanalytics.queryFoldersis part of the shared config. Prefer updating the config typing (or adding a narrow runtime guard) and then accessconfig.analytics?.queryFoldersdirectly.
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 thatanalytics.queryFoldersis part of the schema. Please update config typings (or add a narrow runtime guard) and accessconfig.analytics?.queryFoldersdirectly.
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>
There was a problem hiding this comment.
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
toEqualwith 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 usingrunAsynchronouslyinstead of.catch(() => {}).While the comment explains that errors are handled inside
runQuery, the empty catch pattern is discouraged. UsingrunAsynchronouslywould 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]);
There was a problem hiding this comment.
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 | 🟠 MajorDon’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();
apps/backend/prisma/migrations/20260202000000_fix_trusted_domains_config/migration.sql
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Outdated
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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()withrunAsynchronouslyWithAlert.The empty
.catch(() => {})silently swallows any errors that escaperunQuery's internal try-catch. Per coding guidelines, avoid.catch(console.error)or similar patterns.Since
runQueryalready handles expected errors viasetError(), userunAsynchronouslyWithAlertto 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>
There was a problem hiding this comment.
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 | 🟠 MajorReplace
.catchsuppression withrunAsynchronouslyWithAlert.Line 447 uses
.catch(() => { ... })to suppress promise errors, which violates the coding guideline. SincehandleRetryis an interactive error-recovery callback in the UI (line 478), userunAsynchronouslyWithAlert(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-erroror 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));
.../dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/queries/page-client.tsx
Show resolved
Hide resolved
| } | ||
| if (!isObjectLike(baseValue)) { | ||
| if (lastWasFunction && mergeValue == null) { | ||
| set(res, key, null); |
There was a problem hiding this comment.
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.
|
|
||
| // 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); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 INDEXshould be schema-qualified for consistency.The
CREATE INDEXon Line 26 targets/* SCHEMA_NAME_SENTINEL */."EnvironmentConfigOverride", but theDROP INDEXon Line 116 uses an unqualified index name. While Postgres resolves index names viasearch_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: ThelastWasFunctiontracking logic is difficult to follow.The comma-operator expression on Line 763 that sets
lastWasFunction = trueas 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/elsefor 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: Uncheckedas RowData[]cast on API response.
response.resultis cast toRowData[]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 anArray.isArraycheck.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."
| 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]); |
There was a problem hiding this comment.
🧩 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 -5Repository: 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 -10Repository: 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 -40Repository: stack-auth/stack-auth
Length of output: 92
🏁 Script executed:
# Find CompleteConfig definition
rg -n "type CompleteConfig|export.*CompleteConfig" -A 20 --type ts | head -80Repository: stack-auth/stack-auth
Length of output: 30389
🏁 Script executed:
# Find ConfigFolder definition
rg -n "type ConfigFolder|interface ConfigFolder" -A 10 --type tsRepository: 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 -20Repository: 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 -100Repository: 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 -150Repository: 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.tsRepository: 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 -100Repository: 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 2Repository: 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 -20Repository: 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 -40Repository: 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 configRepository: 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 -20Repository: 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 -10Repository: 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.tsRepository: 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 -100Repository: 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:
- Remove the cast if types have been regenerated correctly, or
- Regenerate the bundled type definitions so that
CompleteConfigproperly includes theanalyticsfield
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.
There was a problem hiding this comment.
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 }) { |
There was a problem hiding this comment.
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)
| open, | ||
| onOpenChange, | ||
| }: { | ||
| row: RowData | null, |
There was a problem hiding this comment.
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.
| <TrashIcon className="h-3 w-3 text-red-500" /> | ||
| </button> | ||
| ); | ||
| } |
There was a problem hiding this comment.


Note
Medium Risk
Introduces new persisted analytics config surface area and changes default-merging/validation behavior (including disallowing dotted
trustedDomainsupdates), 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 aQueriesitem, and the command-paletteRunQuerypreview 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 nowreplaceAllschema sentinels, a new Prisma migration backfills missing parent keys for dotted trusted-domain overrides inEnvironmentConfigOverride, 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
Bug Fixes
Documentation
Tests