Skip to content

Custom queries#1006

Open
goldflag wants to merge 4 commits into
masterfrom
custom-queries
Open

Custom queries#1006
goldflag wants to merge 4 commits into
masterfrom
custom-queries

Conversation

@goldflag
Copy link
Copy Markdown
Collaborator

@goldflag goldflag commented May 22, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Custom query generation: Generate SQL queries from natural language prompts
    • Query execution and results display: Run queries and view results in an interactive, sortable table
    • Multi-tab query editor with syntax highlighting and formatting controls
    • Added Query navigation tab for quick access to the query feature

Review Change Stack

goldflag added 4 commits May 22, 2026 08:28
This commit modifies the MAX_LINK_HEIGHT constant to 56 and updates the nodeGap, minHeight, and verticalPadding values for improved layout and spacing in the SankeyDiagram component. These changes enhance the visual representation of the diagram, ensuring better usability and aesthetics.
This commit updates the `generateCustomQuery` and `runCustomQuery` functions to accept an optional `AbortSignal`, allowing for better control over request cancellation. Additionally, the `useGenerateCustomQuery` hook is modified to pass the signal, improving the overall responsiveness of the custom query feature. The `ResultsTable` component is also introduced, providing enhanced sorting capabilities for query results, thereby improving user experience and data management.
… code

This commit simplifies the `page.tsx` file by removing unnecessary imports and unused functions related to custom query handling. It retains essential hooks and types while enhancing code clarity and maintainability. The overall structure is optimized for better readability, setting the stage for future enhancements.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

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

Project Deployment Actions Updated (UTC)
rybbit Ready Ready Preview, Comment May 22, 2026 4:56pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

This PR introduces a complete AI-powered custom analytics query interface. The implementation spans server-side OpenRouter integration and query validation, client-side API endpoints and React hooks, and a sophisticated multi-tab query editor with AI-assisted SQL generation, execution, and tabular result display.

Changes

Query Editor and Execution Feature

Layer / File(s) Summary
Localization updates across supported languages
client/messages/*.json (cs, de, en, es, fr, it, ja, ko, pl, pt, uk, zh)
Translation key reorganizations and new query-related UI strings added across 12 language files.
OpenRouter library enhancement with error handling and abort support
server/src/lib/openrouter.ts, server/src/lib/openrouter.test.ts
Refactored OpenRouter client with typed error codes, metadata collection, abort signal support, and new callOpenRouterWithMetadata function; includes tests for response handling.
Query validation and security enforcement
server/src/api/analytics/utils/customQueryValidation.ts, server/src/api/analytics/runCustomQuery.test.ts
SQL parser/validator that strips literals and comments, enforces scoped table access via scoped_events, and blocks dangerous keywords and table functions.
Query generation and execution endpoints
server/src/api/analytics/generateCustomQuery.ts, server/src/api/analytics/runCustomQuery.ts
Fastify handlers for generateCustomQuery (uses OpenRouter to generate SQL from prompts) and runCustomQuery (validates and executes scoped queries against ClickHouse), with request validation and error handling.
Server API wiring and route registration
server/src/api/analytics/index.ts, server/src/index.ts
Re-exports query endpoints from analytics module and registers POST routes for query generation and execution under organization scope.
Client API definitions and React Query hooks
client/src/api/analytics/endpoints/customQuery.ts, client/src/api/analytics/endpoints/index.ts, client/src/api/analytics/hooks/useCustomQuery.ts
Type definitions for query requests/responses and custom React Query mutation hooks for useRunCustomQuery and useGenerateCustomQuery.
Client-side query types, utilities, and helpers
client/src/app/[site]/query/types.ts, client/src/app/[site]/query/utils.ts
Shared type definitions for QueryTab and SortState, plus utilities for tab creation, cell formatting, column derivation, stable row sorting, error detection, query formatting, and sort state progression.
QueryEditor: syntax-highlighted SQL input with line numbers
client/src/app/[site]/query/components/QueryEditor.tsx
React component that renders a code editor with SQL syntax highlighting, scroll-synchronized line numbers, theme-aware styling, format/run buttons, and live line/character counts.
QueryPromptForm and QueryTabs navigation
client/src/app/[site]/query/components/QueryPromptForm.tsx, client/src/app/[site]/query/components/QueryTabs.tsx
Components for prompt input with generate button (QueryPromptForm) and multi-tab query navigation with active/busy/error states and tab lifecycle management (QueryTabs).
ResultsPanel and ResultsTable for query output
client/src/app/[site]/query/components/ResultsPanel.tsx, client/src/app/[site]/query/components/ResultsTable.tsx
Components for displaying query results: error panels, formatted row counts, sortable virtualized table grid with horizontal scroll sync, and empty-state messaging.
QueryPage: main orchestration and state management
client/src/app/[site]/query/page.tsx
Next.js client page component that manages multi-tab query state, generation/execution mutations, abort controllers, user interactions, and wires all query subcomponents together with per-tab busy tracking.
Query tab routing and sidebar integration
client/src/app/[site]/components/Sidebar/Sidebar.tsx, client/src/app/[site]/[privateKey]/query/page.tsx, client/src/app/[site]/layout.tsx
Adds "Query" tab to main navigation sidebar with Database icon, wires private-key routing delegation, and adjusts layout footer visibility for new query route.
Minor UI layout adjustments
client/src/app/[site]/journeys/components/SankeyDiagram.tsx, client/src/app/[site]/journeys/page.tsx
Reduces Sankey diagram spacing/link heights and adjusts Journeys page loading state styling for tighter vertical spacing.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • rybbit-io/rybbit#655: Query tab routing depends on the getTabPath/isActiveTab routing helpers that were updated to handle optional private-key URL segments.
  • rybbit-io/rybbit#757: Similar sidebar navigation pattern: both PRs add new tabs (Query vs API Playground) using the shared tab-wiring infrastructure.
  • rybbit-io/rybbit#496: Both PRs modify sidebar layout and styling, with structural changes to navigation item rendering.

Poem

🐰 A query awaits in the editor's glow,
AI whispers SQL words, let the data flow,
Tabs multiply freely, results dance and align,
With syntax-bright code and a table so fine—
The rabbit hops forth to query the logs! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.50% 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 "Custom queries" directly and clearly summarizes the main feature addition across the changeset, which implements a complete custom query system with UI components, API endpoints, validation, and multi-language 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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch custom-queries

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.

Copy link
Copy Markdown
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: 13

🧹 Nitpick comments (1)
client/src/app/[site]/query/page.tsx (1)

3-3: ⚡ Quick win

Reconcile next-intl i18n hook usage (useExtracted vs useTranslations)

client/src/app/[site]/query/page.tsx uses useExtracted() and calls it for user-facing strings (e.g., t("Failed to generate query")). The repo’s client/src/app codebase also consistently uses useExtracted (no useTranslations imports found there), while useExtracted is documented by next-intl as experimental. Align the coding guideline with the project’s intended hook choice, or migrate this page (and potentially others) to useTranslations() if that standard is required.

🤖 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 `@client/src/app/`[site]/query/page.tsx at line 3, The page imports and uses
the experimental next-intl hook useExtracted and then calls it for user-visible
strings (e.g., t("Failed to generate query")); update this file to match the
project's chosen i18n hook: either replace useExtracted with the stable
useTranslations throughout page.tsx (and call the returned t function) or, if
the project intentionally standardizes on useExtracted, update project
guidelines and ensure all usages are consistent; locate the import statement for
useExtracted and the calls to t(...) in page.tsx and change the import to
useTranslations (and adjust any hook call signature if needed) or switch other
files to useExtracted to maintain consistency.
🤖 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 `@client/messages/cs.json`:
- Around line 482-490: The Czech locale file contains new English strings that
need translation; update the entries with keys "k1sXXZ", "SyINSq", "2Y2tgR",
"Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ", and "ICdFJO" to their proper
Czech translations so the query flow shows localized text; locate these keys in
client/messages/cs.json and replace the English values with accurate Czech
phrases (preserve placeholders like {count}) and ensure
punctuation/capitalization matches existing Czech locale style.

In `@client/messages/it.json`:
- Around line 482-490: The listed message keys ("k1sXXZ", "SyINSq", "2Y2tgR",
"Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ", "ICdFJO") in
client/messages/it.json are still in English; replace each value with the proper
Italian translation (e.g., "Failed to generate query" → "Impossibile generare la
query", "Failed to run query" → "Imeccecorre l'esecuzione della query" or
correct Italian, "Top pages this week" → "Pagine principali di questa
settimana", "Generate" → "Genera", "Results" → "Risultati", "{count} rows" →
"{count} righe", "Not run" → "Non eseguito", "No rows returned" → "Nessuna riga
restituita", "Run a query" → "Esegui una query"); update the values for the
specified keys so the Italian locale contains fully translated strings and save
the file.

In `@client/messages/ja.json`:
- Around line 482-490: The Japanese locale file contains English strings for the
query UI keys k1sXXZ, SyINSq, 2Y2tgR, Pc+tM3, yaMHMB, u3e151, 0eYAOP, uDXLhQ,
and ICdFJO; update ja.json by replacing each value with the appropriate Japanese
translation (e.g., "Failed to generate query" → "クエリの生成に失敗しました", "Failed to run
query" → "クエリの実行に失敗しました", "Top pages this week" → "今週の上位ページ", "Generate" → "生成",
"Results" → "結果", "{count} rows" → "{count} 行", "Not run" → "未実行", "No rows
returned" → "結果が返されませんでした", "Run a query" → "クエリを実行"); keep the keys unchanged
and ensure JSON syntax remains valid.

In `@client/messages/ko.json`:
- Around line 482-490: Replace the English values in ko.json for the query UI
keys with Korean translations: set "k1sXXZ" to "쿼리 생성에 실패했습니다", "SyINSq" to "쿼리
실행에 실패했습니다", "2Y2tgR" to "이번 주 인기 페이지", "Pc+tM3" to "생성", "yaMHMB" to "결과",
"u3e151" to "{count}개 행", "0eYAOP" to "실행되지 않음", "uDXLhQ" to "반환된 행이 없습니다", and
"ICdFJO" to "쿼리 실행" so the Korean locale no longer mixes English in the query
feature.

In `@client/messages/pl.json`:
- Around line 482-490: The Polish locale file contains untranslated English
strings for query-related keys; update the values for keys "k1sXXZ", "SyINSq",
"2Y2tgR", "Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ", and "ICdFJO" in
client/messages/pl.json with accurate Polish translations (e.g., "Nie udało się
wygenerować zapytania", "Błąd wykonania zapytania", "Najpopularniejsze strony w
tym tygodniu", "Generuj", "Wyniki", "{count} wierszy", "Nie uruchomiono", "Brak
zwróconych wierszy", "Uruchom zapytanie" or equivalents) so the file is fully
localized; keep the keys unchanged and only replace the string values.

In `@client/messages/pt.json`:
- Around line 482-490: The Portuguese locale file contains untranslated entries
for the query UI; update the values for keys "k1sXXZ", "SyINSq", "2Y2tgR",
"Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ", and "ICdFJO" in
client/messages/pt.json by replacing the English strings with their Portuguese
equivalents (e.g., "Failed to generate query" → "Falha ao gerar consulta",
"Failed to run query" → "Falha ao executar consulta", "Top pages this week" →
"Páginas principais desta semana", "Generate" → "Gerar", "Results" →
"Resultados", "{count} rows" → "{count} linhas", "Not run" → "Não executado",
"No rows returned" → "Nenhuma linha retornada", "Run a query" → "Execute uma
consulta") so the query feature is fully localized.

In `@client/messages/uk.json`:
- Line 75: The Ukrainian locale file has an empty translation for key "qj71j1"
in client/messages/uk.json which leaves the query navigation label blank; update
the value for "qj71j1" with the appropriate Ukrainian translation (matching the
intent of the Chinese "查询"), e.g., a short term like "Запит" or another approved
translation used across locales, ensuring the key remains present and consistent
with other messages/*.json files.
- Around line 482-490: The listed locale entries (keys k1sXXZ, SyINSq, 2Y2tgR,
Pc+tM3, yaMHMB, u3e151, 0eYAOP, uDXLhQ, ICdFJO) are still in English; replace
each English value with the correct Ukrainian translation while preserving keys
and any placeholders (e.g., "{count}") and punctuation, and ensure the same
translated strings are applied consistently across other locale files for these
keys; update the values for k1sXXZ (Failed to generate query), SyINSq (Failed to
run query), 2Y2tgR (Top pages this week), Pc+tM3 (Generate), yaMHMB (Results),
u3e151 ("{count} rows"), 0eYAOP (Not run), uDXLhQ (No rows returned), and ICdFJO
(Run a query) with their Ukrainian equivalents.

In `@client/messages/zh.json`:
- Around line 482-490: The keys k1sXXZ, SyINSq, 2Y2tgR, Pc+tM3, yaMHMB, u3e151,
0eYAOP, uDXLhQ, and ICdFJO in client/messages/zh.json are still in English;
replace each value with the proper Chinese translation for the query feature so
the Chinese locale shows localized UI text. Open client/messages/zh.json, locate
those keys (e.g., "k1sXXZ": "Failed to generate query") and update the string
values to their Chinese equivalents, ensuring phrasing matches other locale
files and keeping the same keys unchanged for consistency across
messages/*.json.

In `@client/src/api/analytics/endpoints/customQuery.ts`:
- Around line 31-38: Rename the endpoint functions to the approved API prefixes
and update their exports: change runCustomQuery to a create-style name (e.g.,
createCustomQueryExecution) and change generateCustomQuery to
createCustomQueryGeneration (or another approved 'fetch/create/update/delete'
form), keeping their parameters and return types intact; update the exported
identifiers in this module to use the new names and update any internal
re-exports/usages that import runCustomQuery or generateCustomQuery so they
reference createCustomQueryExecution and createCustomQueryGeneration instead
(ensure authedFetch call and signature, including optional signal on
generateCustomQuery, remain unchanged).

In `@client/src/app/`[site]/query/components/QueryEditor.tsx:
- Around line 63-64: The hardcoded UI strings in QueryEditor.tsx (e.g., the
button title/aria-label "Format query" and the unit labels "lines" and "chars"
around the editor) must be replaced with next-intl translations: import and call
useTranslations() at the top of the QueryEditor component, replace literal
strings with t('queryEditor.formatQuery'), t('queryEditor.lines') and
t('queryEditor.chars') (or similar keys), and add those keys to messages/en.json
(and other locale files). Also update the other occurrences mentioned (around
lines 141-142) to use the same t(...) keys so all UI text in this component is
localized.

In `@client/src/app/`[site]/query/components/QueryTabs.tsx:
- Around line 34-63: The tab item currently renders a <button> containing an
inner span with role="button" which creates invalid nested interactive controls
and prevents keyboard access to the close control; change the outer <button> to
a non-button container (e.g., <div> or <li>) with role="tab", tabIndex={0}, and
an onKeyDown handler that calls onSelectTab(tab.id) when Enter/Space is pressed
and set aria-selected based on isActive, then replace the inner span
(role="button") with a real <button type="button"> that calls
event.stopPropagation() and onCloseTab(tab.id), keeps aria-label="Close query
tab", and remains focusable; keep references to onSelectTab, onCloseTab, tab.id,
isActive, tabs.length and the existing aria labels/visual classes when making
these edits.

In `@server/src/api/analytics/utils/customQueryValidation.ts`:
- Around line 193-211: The current validation misses comma-separated table
references (e.g., "FROM scoped_events, other_table") because
tableReferencePattern only matches FROM/JOIN keywords and the blockedTableNames
check is limited; update validation to reject any table identifier in a FROM
clause that is not "scoped_events" or a known CTE. Specifically, enhance the
logic around compactQuery: either extend tableReferencePattern to also capture
comma-separated identifiers (e.g., treat commas after a FROM/JOIN as additional
table tokens) or add a second pass that extracts the FROM clause(s) by regex and
splits on commas, then for each resulting identifier check against scoped_events
and cteNames; use the existing symbols blockedTableNames, compactQuery,
tableReferencePattern, and cteNames to locate where to add this extra validation
and return the same "Queries can only read from scoped_events" message on
violation.

---

Nitpick comments:
In `@client/src/app/`[site]/query/page.tsx:
- Line 3: The page imports and uses the experimental next-intl hook useExtracted
and then calls it for user-visible strings (e.g., t("Failed to generate
query")); update this file to match the project's chosen i18n hook: either
replace useExtracted with the stable useTranslations throughout page.tsx (and
call the returned t function) or, if the project intentionally standardizes on
useExtracted, update project guidelines and ensure all usages are consistent;
locate the import statement for useExtracted and the calls to t(...) in page.tsx
and change the import to useTranslations (and adjust any hook call signature if
needed) or switch other files to useExtracted to maintain consistency.
🪄 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: 6e0127a7-8a93-4c16-8b1b-fe08166cc290

📥 Commits

Reviewing files that changed from the base of the PR and between b6feddb and 5c0ecdc.

📒 Files selected for processing (36)
  • client/messages/cs.json
  • client/messages/de.json
  • client/messages/en.json
  • client/messages/es.json
  • client/messages/fr.json
  • client/messages/it.json
  • client/messages/ja.json
  • client/messages/ko.json
  • client/messages/pl.json
  • client/messages/pt.json
  • client/messages/uk.json
  • client/messages/zh.json
  • client/src/api/analytics/endpoints/customQuery.ts
  • client/src/api/analytics/endpoints/index.ts
  • client/src/api/analytics/hooks/useCustomQuery.ts
  • client/src/app/[site]/[privateKey]/query/page.tsx
  • client/src/app/[site]/components/Sidebar/Sidebar.tsx
  • client/src/app/[site]/journeys/components/SankeyDiagram.tsx
  • client/src/app/[site]/journeys/page.tsx
  • client/src/app/[site]/layout.tsx
  • client/src/app/[site]/query/components/QueryEditor.tsx
  • client/src/app/[site]/query/components/QueryPromptForm.tsx
  • client/src/app/[site]/query/components/QueryTabs.tsx
  • client/src/app/[site]/query/components/ResultsPanel.tsx
  • client/src/app/[site]/query/components/ResultsTable.tsx
  • client/src/app/[site]/query/page.tsx
  • client/src/app/[site]/query/types.ts
  • client/src/app/[site]/query/utils.ts
  • server/src/api/analytics/generateCustomQuery.ts
  • server/src/api/analytics/index.ts
  • server/src/api/analytics/runCustomQuery.test.ts
  • server/src/api/analytics/runCustomQuery.ts
  • server/src/api/analytics/utils/customQueryValidation.ts
  • server/src/index.ts
  • server/src/lib/openrouter.test.ts
  • server/src/lib/openrouter.ts

Comment thread client/messages/cs.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the newly added query strings for Czech users.

These new entries are in English in a Czech locale file, which will surface mixed-language UI in the query flow.

🤖 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 `@client/messages/cs.json` around lines 482 - 490, The Czech locale file
contains new English strings that need translation; update the entries with keys
"k1sXXZ", "SyINSq", "2Y2tgR", "Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ",
and "ICdFJO" to their proper Czech translations so the query flow shows
localized text; locate these keys in client/messages/cs.json and replace the
English values with accurate Czech phrases (preserve placeholders like {count})
and ensure punctuation/capitalization matches existing Czech locale style.

Comment thread client/messages/it.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize new query strings for Italian UI

Lines 482-490 are still English in it.json, which causes mixed-language UI in the query flow. Please translate these values to Italian to keep locale quality consistent.

🤖 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 `@client/messages/it.json` around lines 482 - 490, The listed message keys
("k1sXXZ", "SyINSq", "2Y2tgR", "Pc+tM3", "yaMHMB", "u3e151", "0eYAOP", "uDXLhQ",
"ICdFJO") in client/messages/it.json are still in English; replace each value
with the proper Italian translation (e.g., "Failed to generate query" →
"Impossibile generare la query", "Failed to run query" → "Imeccecorre
l'esecuzione della query" or correct Italian, "Top pages this week" → "Pagine
principali di questa settimana", "Generate" → "Genera", "Results" → "Risultati",
"{count} rows" → "{count} righe", "Not run" → "Non eseguito", "No rows returned"
→ "Nessuna riga restituita", "Run a query" → "Esegui una query"); update the
values for the specified keys so the Italian locale contains fully translated
strings and save the file.

Comment thread client/messages/ja.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate newly added query labels into Japanese

Lines 482-490 use English text in ja.json, so Japanese users will see partial English in this feature. Please add Japanese translations for these entries.

🤖 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 `@client/messages/ja.json` around lines 482 - 490, The Japanese locale file
contains English strings for the query UI keys k1sXXZ, SyINSq, 2Y2tgR, Pc+tM3,
yaMHMB, u3e151, 0eYAOP, uDXLhQ, and ICdFJO; update ja.json by replacing each
value with the appropriate Japanese translation (e.g., "Failed to generate
query" → "クエリの生成に失敗しました", "Failed to run query" → "クエリの実行に失敗しました", "Top pages
this week" → "今週の上位ページ", "Generate" → "生成", "Results" → "結果", "{count} rows" →
"{count} 行", "Not run" → "未実行", "No rows returned" → "結果が返されませんでした", "Run a
query" → "クエリを実行"); keep the keys unchanged and ensure JSON syntax remains
valid.

Comment thread client/messages/ko.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace English placeholders with Korean translations

Lines 482-490 are currently English in ko.json. This introduces mixed-language UI for Korean users in the query feature. Please translate these strings to Korean.

🤖 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 `@client/messages/ko.json` around lines 482 - 490, Replace the English values
in ko.json for the query UI keys with Korean translations: set "k1sXXZ" to "쿼리
생성에 실패했습니다", "SyINSq" to "쿼리 실행에 실패했습니다", "2Y2tgR" to "이번 주 인기 페이지", "Pc+tM3" to
"생성", "yaMHMB" to "결과", "u3e151" to "{count}개 행", "0eYAOP" to "실행되지 않음",
"uDXLhQ" to "반환된 행이 없습니다", and "ICdFJO" to "쿼리 실행" so the Korean locale no
longer mixes English in the query feature.

Comment thread client/messages/pl.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize query-related strings in Polish

Lines 482-490 are in English inside pl.json, which creates a mixed-language experience in the Polish locale. Please translate these values to Polish.

🤖 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 `@client/messages/pl.json` around lines 482 - 490, The Polish locale file
contains untranslated English strings for query-related keys; update the values
for keys "k1sXXZ", "SyINSq", "2Y2tgR", "Pc+tM3", "yaMHMB", "u3e151", "0eYAOP",
"uDXLhQ", and "ICdFJO" in client/messages/pl.json with accurate Polish
translations (e.g., "Nie udało się wygenerować zapytania", "Błąd wykonania
zapytania", "Najpopularniejsze strony w tym tygodniu", "Generuj", "Wyniki",
"{count} wierszy", "Nie uruchomiono", "Brak zwróconych wierszy", "Uruchom
zapytanie" or equivalents) so the file is fully localized; keep the keys
unchanged and only replace the string values.

Comment thread client/messages/zh.json
Comment on lines +482 to +490
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Untranslated strings for query feature.

The new query-related keys (k1sXXZ through ICdFJO) contain English values instead of Chinese translations. This breaks internationalization for Chinese users, who will see English UI text in the query interface.

All user-facing strings must be translated to the target locale.

🌐 Suggested Chinese translations
-  "k1sXXZ": "Failed to generate query",
-  "SyINSq": "Failed to run query",
-  "2Y2tgR": "Top pages this week",
-  "Pc+tM3": "Generate",
-  "yaMHMB": "Results",
-  "u3e151": "{count} rows",
-  "0eYAOP": "Not run",
-  "uDXLhQ": "No rows returned",
-  "ICdFJO": "Run a query",
+  "k1sXXZ": "生成查询失败",
+  "SyINSq": "执行查询失败",
+  "2Y2tgR": "本周热门页面",
+  "Pc+tM3": "生成",
+  "yaMHMB": "结果",
+  "u3e151": "{count} 行",
+  "0eYAOP": "未运行",
+  "uDXLhQ": "未返回任何行",
+  "ICdFJO": "运行查询",

As per coding guidelines, keep locale keys consistent across messages/*.json.

📝 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
"k1sXXZ": "Failed to generate query",
"SyINSq": "Failed to run query",
"2Y2tgR": "Top pages this week",
"Pc+tM3": "Generate",
"yaMHMB": "Results",
"u3e151": "{count} rows",
"0eYAOP": "Not run",
"uDXLhQ": "No rows returned",
"ICdFJO": "Run a query",
"k1sXXZ": "生成查询失败",
"SyINSq": "执行查询失败",
"2Y2tgR": "本周热门页面",
"Pc+tM3": "生成",
"yaMHMB": "结果",
"u3e151": "{count} ",
"0eYAOP": "未运行",
"uDXLhQ": "未返回任何行",
"ICdFJO": "运行查询",
🤖 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 `@client/messages/zh.json` around lines 482 - 490, The keys k1sXXZ, SyINSq,
2Y2tgR, Pc+tM3, yaMHMB, u3e151, 0eYAOP, uDXLhQ, and ICdFJO in
client/messages/zh.json are still in English; replace each value with the proper
Chinese translation for the query feature so the Chinese locale shows localized
UI text. Open client/messages/zh.json, locate those keys (e.g., "k1sXXZ":
"Failed to generate query") and update the string values to their Chinese
equivalents, ensuring phrasing matches other locale files and keeping the same
keys unchanged for consistency across messages/*.json.

Comment on lines +31 to +38
export function runCustomQuery(organizationId: string, query: string) {
return authedFetch<RunCustomQueryResponse>(`/organizations/${organizationId}/analytics/query`, undefined, {
method: "POST",
data: { query },
});
}

export function generateCustomQuery(organizationId: string, data: GenerateCustomQueryRequest, signal?: AbortSignal) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Rename endpoint functions to approved API prefixes.

These endpoint function names don’t follow the repository API naming convention (run*/generate* vs required fetch*/create*/update*/delete*). Please rename and re-export consistently (for example, createCustomQueryExecution, createCustomQueryGeneration or equivalent approved prefix forms).

As per coding guidelines, “API hook functions should be prefixed with 'use' (e.g., useGetSites); endpoint functions should be prefixed with 'fetch', 'create', 'update', or 'delete'”.

Also applies to: 38-48

🤖 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 `@client/src/api/analytics/endpoints/customQuery.ts` around lines 31 - 38,
Rename the endpoint functions to the approved API prefixes and update their
exports: change runCustomQuery to a create-style name (e.g.,
createCustomQueryExecution) and change generateCustomQuery to
createCustomQueryGeneration (or another approved 'fetch/create/update/delete'
form), keeping their parameters and return types intact; update the exported
identifiers in this module to use the new names and update any internal
re-exports/usages that import runCustomQuery or generateCustomQuery so they
reference createCustomQueryExecution and createCustomQueryGeneration instead
(ensure authedFetch call and signature, including optional signal on
generateCustomQuery, remain unchanged).

Comment on lines +63 to +64
title="Format query"
aria-label="Format query"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize remaining hardcoded UI strings

These labels are still hardcoded in English ("Format query", "lines", "chars"), so they won’t be translated with the rest of the page.

💡 Suggested patch
           <Button
             type="button"
             size="smIcon"
             variant="ghost"
             onClick={onFormat}
             disabled={disabled || !value.trim()}
-            title="Format query"
-            aria-label="Format query"
+            title={t("Format query")}
+            aria-label={t("Format query")}
           >
             <AlignLeft className="h-4 w-4" />
           </Button>
@@
       <div className="flex h-7 items-center justify-between border-t border-neutral-150 bg-neutral-50 px-3 text-[11px] text-neutral-500 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-500">
-        <span>{lineCount} lines</span>
-        <span>{value.length.toLocaleString()} chars</span>
+        <span>{t("{count} lines", { count: lineCount.toLocaleString() })}</span>
+        <span>{t("{count} chars", { count: value.length.toLocaleString() })}</span>
       </div>

As per coding guidelines, "Use next-intl's 'useTranslations()' hook for i18n; maintain translation files in messages/ directory (en.json, de.json, etc.)".

Also applies to: 141-142

🤖 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 `@client/src/app/`[site]/query/components/QueryEditor.tsx around lines 63 - 64,
The hardcoded UI strings in QueryEditor.tsx (e.g., the button title/aria-label
"Format query" and the unit labels "lines" and "chars" around the editor) must
be replaced with next-intl translations: import and call useTranslations() at
the top of the QueryEditor component, replace literal strings with
t('queryEditor.formatQuery'), t('queryEditor.lines') and t('queryEditor.chars')
(or similar keys), and add those keys to messages/en.json (and other locale
files). Also update the other occurrences mentioned (around lines 141-142) to
use the same t(...) keys so all UI text in this component is localized.

Comment on lines +34 to +63
<button
key={tab.id}
type="button"
onClick={() => onSelectTab(tab.id)}
className={cn(
"group flex h-8 min-w-[120px] items-center justify-between gap-2 rounded-md border px-2 text-left text-xs transition-colors",
isActive
? "border-neutral-200 bg-white text-neutral-900 shadow-sm dark:border-neutral-750 dark:bg-neutral-900 dark:text-neutral-100"
: "border-transparent text-neutral-600 hover:bg-white/70 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-900/70 dark:hover:text-neutral-100"
)}
>
<span className="flex min-w-0 items-center gap-1.5">
{tabIsBusy && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-neutral-400" />}
{tab.resultError && !tabIsBusy && <AlertCircle className="h-3 w-3 shrink-0 text-red-500" />}
<span className="truncate">{tab.name || `Query ${index + 1}`}</span>
</span>
{tabs.length > 1 && (
<span
role="button"
tabIndex={-1}
onClick={event => {
event.stopPropagation();
onCloseTab(tab.id);
}}
className="rounded p-0.5 text-neutral-400 opacity-70 hover:bg-neutral-100 hover:text-neutral-700 group-hover:opacity-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
aria-label="Close query tab"
>
<X className="h-3 w-3" />
</span>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix tab close control accessibility and invalid interactive nesting

The close action is an unfocusable pseudo-button inside another button. Keyboard users cannot trigger close, and nested interactive controls are invalid markup.

💡 Suggested structural fix
-          <button
+          <div
             key={tab.id}
-            type="button"
-            onClick={() => onSelectTab(tab.id)}
             className={cn(
               "group flex h-8 min-w-[120px] items-center justify-between gap-2 rounded-md border px-2 text-left text-xs transition-colors",
               isActive
                 ? "border-neutral-200 bg-white text-neutral-900 shadow-sm dark:border-neutral-750 dark:bg-neutral-900 dark:text-neutral-100"
                 : "border-transparent text-neutral-600 hover:bg-white/70 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-900/70 dark:hover:text-neutral-100"
             )}
           >
-            <span className="flex min-w-0 items-center gap-1.5">
+            <button type="button" onClick={() => onSelectTab(tab.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
               {tabIsBusy && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-neutral-400" />}
               {tab.resultError && !tabIsBusy && <AlertCircle className="h-3 w-3 shrink-0 text-red-500" />}
               <span className="truncate">{tab.name || `Query ${index + 1}`}</span>
-            </span>
+            </button>
             {tabs.length > 1 && (
-              <span
-                role="button"
-                tabIndex={-1}
+              <button
+                type="button"
                 onClick={event => {
                   event.stopPropagation();
                   onCloseTab(tab.id);
                 }}
                 className="rounded p-0.5 text-neutral-400 opacity-70 hover:bg-neutral-100 hover:text-neutral-700 group-hover:opacity-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
                 aria-label="Close query tab"
               >
                 <X className="h-3 w-3" />
-              </span>
+              </button>
             )}
-          </button>
+          </div>
📝 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
<button
key={tab.id}
type="button"
onClick={() => onSelectTab(tab.id)}
className={cn(
"group flex h-8 min-w-[120px] items-center justify-between gap-2 rounded-md border px-2 text-left text-xs transition-colors",
isActive
? "border-neutral-200 bg-white text-neutral-900 shadow-sm dark:border-neutral-750 dark:bg-neutral-900 dark:text-neutral-100"
: "border-transparent text-neutral-600 hover:bg-white/70 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-900/70 dark:hover:text-neutral-100"
)}
>
<span className="flex min-w-0 items-center gap-1.5">
{tabIsBusy && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-neutral-400" />}
{tab.resultError && !tabIsBusy && <AlertCircle className="h-3 w-3 shrink-0 text-red-500" />}
<span className="truncate">{tab.name || `Query ${index + 1}`}</span>
</span>
{tabs.length > 1 && (
<span
role="button"
tabIndex={-1}
onClick={event => {
event.stopPropagation();
onCloseTab(tab.id);
}}
className="rounded p-0.5 text-neutral-400 opacity-70 hover:bg-neutral-100 hover:text-neutral-700 group-hover:opacity-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
aria-label="Close query tab"
>
<X className="h-3 w-3" />
</span>
)}
<div
key={tab.id}
className={cn(
"group flex h-8 min-w-[120px] items-center justify-between gap-2 rounded-md border px-2 text-left text-xs transition-colors",
isActive
? "border-neutral-200 bg-white text-neutral-900 shadow-sm dark:border-neutral-750 dark:bg-neutral-900 dark:text-neutral-100"
: "border-transparent text-neutral-600 hover:bg-white/70 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-900/70 dark:hover:text-neutral-100"
)}
>
<button type="button" onClick={() => onSelectTab(tab.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
{tabIsBusy && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-neutral-400" />}
{tab.resultError && !tabIsBusy && <AlertCircle className="h-3 w-3 shrink-0 text-red-500" />}
<span className="truncate">{tab.name || `Query ${index + 1}`}</span>
</button>
{tabs.length > 1 && (
<button
type="button"
onClick={event => {
event.stopPropagation();
onCloseTab(tab.id);
}}
className="rounded p-0.5 text-neutral-400 opacity-70 hover:bg-neutral-100 hover:text-neutral-700 group-hover:opacity-100 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
aria-label="Close query tab"
>
<X className="h-3 w-3" />
</button>
)}
</div>
🤖 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 `@client/src/app/`[site]/query/components/QueryTabs.tsx around lines 34 - 63,
The tab item currently renders a <button> containing an inner span with
role="button" which creates invalid nested interactive controls and prevents
keyboard access to the close control; change the outer <button> to a non-button
container (e.g., <div> or <li>) with role="tab", tabIndex={0}, and an onKeyDown
handler that calls onSelectTab(tab.id) when Enter/Space is pressed and set
aria-selected based on isActive, then replace the inner span (role="button")
with a real <button type="button"> that calls event.stopPropagation() and
onCloseTab(tab.id), keeps aria-label="Close query tab", and remains focusable;
keep references to onSelectTab, onCloseTab, tab.id, isActive, tabs.length and
the existing aria labels/visual classes when making these edits.

Comment on lines +193 to +211
for (const tableName of blockedTableNames) {
if (new RegExp(`,\\s*${tableName}\\b`, "i").test(compactQuery)) {
return "Queries can only read from scoped_events";
}
}

const tableReferencePattern = /\b(?:FROM|JOIN)\s+([a-zA-Z_][a-zA-Z0-9_.]*|\()/gi;
let match: RegExpExecArray | null;
while ((match = tableReferencePattern.exec(compactQuery)) !== null) {
const tableName = match[1];
if (tableName === "(") {
continue;
}

const normalizedTableName = tableName.toLowerCase();
if (normalizedTableName !== "scoped_events" && !cteNames.has(normalizedTableName)) {
return "Queries can only read from scoped_events";
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Comma-join bypass for unlisted tables.

The comma-join check at lines 193-197 only blocks tables in blockedTableNames. The FROM/JOIN pattern at 199-211 only captures table references preceded by FROM or JOIN keywords. A query like SELECT * FROM scoped_events, some_other_table would bypass both checks if some_other_table exists but isn't in blockedTableNames.

Consider extending the FROM/JOIN pattern to also capture comma-separated table references, or add a catch-all check for any table reference that isn't scoped_events or a CTE.

🔒 Proposed fix
-  const tableReferencePattern = /\b(?:FROM|JOIN)\s+([a-zA-Z_][a-zA-Z0-9_.]*|\()/gi;
+  // Match tables after FROM, JOIN, or comma (within FROM clause context)
+  const tableReferencePattern = /\b(?:FROM|JOIN)\s+([a-zA-Z_][a-zA-Z0-9_.]*|\()|,\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*(?:AS\s+\w+\s*)?(?=,|\s+(?:ON|WHERE|GROUP|ORDER|LIMIT|UNION|EXCEPT|INTERSECT|$)|\s*$)/gi;
   let match: RegExpExecArray | null;
   while ((match = tableReferencePattern.exec(compactQuery)) !== null) {
-    const tableName = match[1];
+    const tableName = match[1] || match[2];
     if (tableName === "(") {
       continue;
     }

Alternatively, adopt a simpler allowlist approach: reject any identifier that looks like a table reference (follows FROM/JOIN or comma in a FROM clause) unless it's exactly scoped_events or a known CTE.

🧰 Tools
🪛 ast-grep (0.42.2)

[warning] 193-193: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(,\\s*${tableName}\\b, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 OpenGrep (1.21.0)

[ERROR] 201-201: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🤖 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 `@server/src/api/analytics/utils/customQueryValidation.ts` around lines 193 -
211, The current validation misses comma-separated table references (e.g., "FROM
scoped_events, other_table") because tableReferencePattern only matches
FROM/JOIN keywords and the blockedTableNames check is limited; update validation
to reject any table identifier in a FROM clause that is not "scoped_events" or a
known CTE. Specifically, enhance the logic around compactQuery: either extend
tableReferencePattern to also capture comma-separated identifiers (e.g., treat
commas after a FROM/JOIN as additional table tokens) or add a second pass that
extracts the FROM clause(s) by regex and splits on commas, then for each
resulting identifier check against scoped_events and cteNames; use the existing
symbols blockedTableNames, compactQuery, tableReferencePattern, and cteNames to
locate where to add this extra validation and return the same "Queries can only
read from scoped_events" message on violation.

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