Skip to content

feat(history): show aggregated query performance insights and regressions (#1413) - #1441

Open
markankamaransheogarat-sys wants to merge 1 commit into
TabularisDB:mainfrom
markankamaransheogarat-sys:feat/query-history-insights-1413
Open

markankamaransheogarat-sys wants to merge 1 commit into
TabularisDB:mainfrom
markankamaransheogarat-sys:feat/query-history-insights-1413

Conversation

@markankamaransheogarat-sys

@markankamaransheogarat-sys markankamaransheogarat-sys commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1413

Summary of Changes

This PR adds an Insights view to Query History that aggregates recorded query executions by normalized query shape and surfaces client-observed performance metrics and period-over-period regressions:

  1. Dialect-Aware Query Shape Normalization (src-tauri/src/query_history.rs & packages/web-ui/src/utils/queryHistoryInsights.ts):

    • Strips line comments (--, plus # in MySQL while respecting MySQL's -- whitespace rule) and block comments (/* ... */, including nested block comments in PostgreSQL/MSSQL).
    • Replaces string literals ('...', PostgreSQL E'...' and $$...$$, MySQL "...") with '?' and numeric/hex/bit/boolean literals with ?.
    • Collapses literal-only IN (?, ?, ...) lists into IN (?+).
    • Preserves SQL statement verbs (SELECT, INSERT, UPDATE, DELETE, WITH), table/column identifiers, and dialect-quoted identifiers ("...", `...`, [...]).
  2. Bounded Non-Deduplicated Execution Log & Insights Command (src-tauri/src/application/productivity.rs):

    • Maintains a bounded per-connection execution log ({connection_id}.executions.json) alongside the consecutively-deduplicated history view ({connection_id}.json) so repeated executions of the same query are accurately counted in callCount and timing averages while keeping get_query_history backward-compatible.
    • Exposes get_query_history_insights across both Desktop Tauri commands and Web JSON-RPC transport, isolated per connectionId and optional database filter.
  3. Regression Detection & Coverage Disclosure:

    • Compares the selected current window (Last 24h, Last 7d, Last 30d) against the immediately preceding window of equal length.
    • Requires a minimum sample size (MIN_REGRESSION_SAMPLES = 3 timed executions in both current and prior windows), a minimum absolute increase (>= 10ms), and a minimum relative increase (>= +25%) before flagging a shape as regressed; under-sampled shapes are labeled insufficientSamples.
    • Excludes executions without recorded durations (executionTimeMs: null) from timing averages while reporting their count in missingDurationCount.
    • Clearly discloses that timings are client-observed from executions recorded in Tabularis and warns when the history retention cap is reached.
  4. Query History Insights UI & Tests (packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx):

    • Adds an Entries / Insights view toggle in the Query History sidebar section.
    • Supports filtering by search text, database, comparison window, sort order (Regressions first, Total time, Average time, Call count), and Regressions only.
    • Provides expandable Contributing executions drill-down rows that load/run the individual execution on click/double-click.
    • Adds full localization across all 13 supported languages and unit/component tests.

Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Comment thread src-tauri/src/query_history.rs Outdated
_ => {
// Multi-character operators
let two = if i + 1 < len {
Some(&sql[i..i + 2])

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.

CRITICAL: Possible panic on non-ASCII SQL (UTF-8 slice off a char boundary)

&sql[i..i + 2] (and &sql[i..i + 3] below) slice a &str by byte offsets. When an ASCII operator byte (=, <, -, *, ...) is directly followed by a multi-byte character, e.g. WHERE a=é or x<日本 (unquoted non-ASCII identifiers are legal in Postgres/MySQL), i + 2/i + 3 lands inside the character and Rust panics with byte index is not a char boundary. This runs on the Tauri command / web RPC path for every history entry, so one such query in history would make the whole Insights view fail. Compare on bytes instead, e.g. bytes.get(i..i + 2) against b"<=" etc., or use sql.get(i..i + 2) which returns None off a boundary. Please add a test with a non-ASCII identifier.

first.error = error;
let updated = first.clone();
write_query_history(config_dir, connection_id, &entries)?;
let _ = append_query_history_execution(

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.

WARNING: Execution-log write failures are silently discarded

let _ = append_query_history_execution(...) (here and at the non-duplicate path below) drops any I/O or serialization error with no log. Insights then under-count calls with no signal to the user or in logs. The same pattern is used for write_query_history_executions in delete_query_history_entry and backfill_query_history_for_connection, which can leave the executions file out of sync with the visible history (e.g. a deleted entry's SQL stays in the executions log). Please at least log::warn! the error.

merged.push(entry);
}
}
merged.truncate(max_records);

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.

SUGGESTION: merged.truncate(max_records) runs on an unsorted merge

Executions come newest-first, but legacy entries not covered by the execution log are appended afterwards regardless of age, so truncation can drop newer executions in favour of older legacy entries (or vice versa) and total_stored_executions / oldest_recorded_at / retention_cap_reached are computed on that arbitrary subset. Sort the merged list by executed_at before truncating.

setBackendInsights(response);
}
})
.catch(() => {

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.

WARNING: Backend failure silently falls back to different data

When get_query_history_insights rejects, the error is swallowed and the view silently switches to computeQueryHistoryInsights(entries), which runs over the consecutively de-duplicated list with a hard-coded maxStoredExecutions = 500. Call counts, averages and the retention warning then differ from the backend semantics described in the PR, with no indication to the user. Surface the error (or at least a notice) instead of silently substituting different numbers.

<button
type="button"
key={execEntry.id}
onClick={() => onDoubleClick(execEntry)}

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.

SUGGESTION: Single click loads the query into the editor

onClick is wired to the same handler as onDoubleClick, so a stray click on a contributing execution immediately opens/loads it. The Entries list uses click = select, double-click/Enter = open; keeping that behaviour here avoids accidental tab creation.

@kilo-code-bot

kilo-code-bot Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The incremental range since 0a1f229d is almost entirely upstream main merges. In the PR's own query-history files, the only change is the merged-in "search errors" toggle in QueryHistorySection.tsx. It is session-only state and is reset correctly in the clear-filters handler. Earlier findings remain resolved. Tests were not executed (read-only review).

Files Reviewed (25 files in PR diff)
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx
  • packages/web-ui/src/utils/queryHistoryInsights.ts
  • src-tauri/src/query_history.rs
  • src-tauri/src/application/productivity.rs
  • remaining PR files unchanged apart from merge context
Previous Review Summaries (6 snapshots, latest commit 0a1f229)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 0a1f229)

Status: No Issues Found | Recommendation: Merge

The incremental range since the last review (11f5217b) consists almost entirely of upstream main merges. The PR's own query-history insights files are unchanged apart from merge context in shared files (contract.ts, ExplorerSidebar.tsx, transport/desktop/mod.rs, transport/web/rpc.rs), and the PR's additions in those files are consistent with the previously reviewed logic. Earlier findings remain resolved: byte-safe operator matching in query_history.rs, merged executions sorted before truncation, and settled insights result handling in QueryHistorySection.tsx.

Files Reviewed (29 files in PR diff)
  • packages/web-ui/src/api/contract.ts
  • packages/web-ui/src/components/layout/ExplorerSidebar.tsx
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx
  • packages/web-ui/src/i18n/locales/*.json (13 locales)
  • packages/web-ui/src/types/queryHistory.ts
  • packages/web-ui/src/utils/queryHistoryInsights.ts
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx
  • packages/web-ui/tests/utils/queryHistoryInsights.test.ts
  • src-tauri/src/application/productivity.rs
  • src-tauri/src/application/productivity_tests.rs
  • src-tauri/src/query_history.rs
  • src-tauri/src/query_history_tests.rs
  • src-tauri/src/transport/desktop/mod.rs
  • src-tauri/src/transport/desktop/query_history.rs
  • src-tauri/src/transport/web/rpc.rs
  • src-tauri/src/transport/web/rpc_tests.rs
  • src-tauri/src/transport/web/tests.rs

Tests were not executed (read-only review).

Previous review (commit 11f5217)

Status: No Issues Found | Recommendation: Merge

The previous warning (success and error state for the same insights key never reconciled) is resolved. QueryHistorySection.tsx now stores a single settled insightsResult ({key, data} or {key, error}), so a later success clears an earlier error and a later failure clears stale backend data. The new test covers both transitions.

Files Reviewed (2 files in incremental diff)
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx

Tests were not executed (read-only review).

Previous review (commit 75f7620)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx 282 loadedInsights and loadedError are never reconciled for the same key. When entries changes the effect refetches with an unchanged key, so a failed-then-successful fetch leaves the error banner visible next to valid data, and a successful-then-failed fetch keeps stale backend data instead of the fallback. Clear the opposite state when each request settles.
Files Reviewed (2 files in incremental diff)
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx - 1 issue (refactor to key-scoped state; also adds a loading indicator and null-safe contributingExecutions)
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx - connection-switch test, no issues

Tests were not executed (read-only review).

Fix these issues in Kilo Cloud

Previous review (commit 295b6f7)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (16 files in incremental diff)
  • src-tauri/src/application/productivity.rs - execution-log write failures now logged via log::warn!
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx - backend insights failure now surfaced to the user via an alert
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx
  • packages/web-ui/src/i18n/locales/*.json (13 locales) - new historyInsightsBackendError string

Tests were not executed (read-only review).

Previous review (commit b65a352)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
src-tauri/src/application/productivity.rs 695 One execution-log write failure is still silently discarded: let _ = append_query_history_execution(...) in the duplicate-entry path (and let _ = write_query_history_executions(...) at lines 374 and 767 in backfill/delete). The main add path now logs a warning, but these do not, so insights can under-count or keep deleted SQL with no signal.
packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx 259 A backend insights failure is swallowed (.catch(() => setBackendInsights(null))) and the UI silently falls back to local computation over the de-duplicated list (hard-coded cap of 500), showing different numbers with no indication.
Files Reviewed (4 files in incremental diff)
  • src-tauri/src/query_history.rs - previous CRITICAL (UTF-8 slice panic) fixed via byte-based get
  • src-tauri/src/application/productivity.rs - 1 issue (partially fixed; merged list is now sorted before truncation)
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx - 1 issue (single-click-loads suggestion fixed)
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx

Tests were not executed (read-only review).

Fix these issues in Kilo Cloud

Previous review (commit 0184427)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
src-tauri/src/query_history.rs 651 &sql[i..i + 2] / &sql[i..i + 3] slice by byte offset and panic with "not a char boundary" when an ASCII operator is directly followed by a multi-byte character (e.g. a=é). One such history entry breaks the whole Insights request.

WARNING

File Line Issue
src-tauri/src/application/productivity.rs 695 Execution-log write failures (let _ = append_query_history_execution(...), and the write_query_history_executions calls in delete/backfill) are silently discarded, so insights can under-count or keep deleted SQL with no log or signal.
packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx 259 A backend insights failure is swallowed and the UI silently falls back to local computation over the de-duplicated list (hard-coded cap of 500), showing different numbers with no indication.

SUGGESTION

File Line Issue
src-tauri/src/application/productivity.rs 947 merged.truncate(max_records) runs on an unsorted merge of executions and legacy entries, so retention and coverage values are computed on an arbitrary subset. Sort by executed_at first.
packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx 813 Single click on a contributing execution loads it (same handler as double-click), unlike the Entries list.
Files Reviewed (29 files)
  • src-tauri/src/query_history.rs - 1 issue
  • src-tauri/src/application/productivity.rs - 2 issues
  • packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx - 2 issues
  • packages/web-ui/src/utils/queryHistoryInsights.ts
  • packages/web-ui/src/types/queryHistory.ts
  • packages/web-ui/src/api/contract.ts
  • packages/web-ui/src/components/layout/ExplorerSidebar.tsx
  • packages/web-ui/src/i18n/locales/*.json (13 files)
  • packages/web-ui/tests/components/layout/sidebar/QueryHistorySection.test.tsx
  • packages/web-ui/tests/utils/queryHistoryInsights.test.ts
  • src-tauri/src/application/productivity_tests.rs
  • src-tauri/src/query_history_tests.rs
  • src-tauri/src/transport/desktop/mod.rs
  • src-tauri/src/transport/desktop/query_history.rs
  • src-tauri/src/transport/web/rpc.rs
  • src-tauri/src/transport/web/rpc_tests.rs
  • src-tauri/src/transport/web/tests.rs

Notes: the PR also contains unrelated rustfmt-only reformatting (transport/web/tests.rs, rpc_tests.rs, desktop/mod.rs, parts of productivity.rs), which makes the diff noisier than necessary. Tests were not executed (read-only review).

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5.5 · Input: 256 · Output: 1.1K · Cached: 167.7K

Review guidance: REVIEW.md from base branch main

@markankamaransheogarat-sys
markankamaransheogarat-sys force-pushed the feat/query-history-insights-1413 branch 2 times, most recently from b65a352 to 295b6f7 Compare November 8, 2026 09:47

@debba debba left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for adding a separate, non-deduplicated execution log and for making the retention/sample limits visible in Insights. That avoids treating the existing deduplicated history as a complete execution record. The 34 targeted frontend tests and typecheck pass on this branch.

The Insights request state needs to be tied to its connection and filters before this is merged. QueryHistorySection retains backendInsights while a new request is pending, and insightsData always prefers that retained response over fallbackInsights. If connection A has loaded, then the user switches to B or changes the database/window, A's old aggregates remain visible under the new selection until the next request settles. The cancellation flag prevents an old response arriving late, but does not invalidate the response already stored. Please key the stored response by the request identity, and show only matching data or a loading state. Add a test that loads A, switches to B with its response deliberately unresolved, and checks that A's values are no longer displayed.

The new effect also calls setBackendInsights/setInsightsError synchronously. Please restructure this along with the request state to follow .rules/react.md rule 2; state updates after the async result are fine.

I have not completed a real-app test of the Rust execution-log path. The host Rust build currently stops at missing pkg-config/GLib prerequisites, so the frontend test results above should not be read as backend validation.

@markankamaransheogarat-sys

Copy link
Copy Markdown
Contributor Author

Thanks for catching this edge case, @debba!

Addressed in 75f7620:

  • Connection-Keyed Insights State: Keyed the loaded backend insights response and error by . When switching connections (or windows/databases), the prior connection's backend insights are never retained while the pending promise resolves.
  • Rule 2 Compliance: Zero synchronous inside . Invalidation and loading state are derived during rendering (), ensuring clean rendering lifecycle without cascading updates.
  • Loading State & Status Region: Added an accessible loading indicator with while fresh insights are in-flight for the active connection.
  • Regression Test: Added unit test verifying that connection A insights are immediately replaced by the pending/loading state upon switching to connection B.

@markankamaransheogarat-sys

Copy link
Copy Markdown
Contributor Author

Thanks for catching this edge case, @debba!

Addressed in commit 75f7620:

  • Connection-Keyed Insights State: Keyed the loaded backend insights response and error by connectionId:database:insightsWindow:dialect. When switching connections (or windows/databases), the prior connection's backend insights are never retained while the pending promise resolves.
  • Rule 2 Compliance: Zero synchronous setState inside useEffect. Invalidation and loading state are derived during rendering (loadedInsights?.key === currentInsightsKey), ensuring a clean rendering lifecycle without cascading updates.
  • Loading State & Status Region: Added an accessible loading indicator with role="status" while fresh insights are in-flight for the active connection.
  • Regression Test: Added unit test does not retain connection A insights when switching to connection B with pending response verifying that connection A insights are immediately replaced by the pending/loading state upon switching to connection B.

})
.then((response) => {
if (!cancelled) {
setLoadedInsights({key: requestKey, data: response});

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.

WARNING: Success and error state for the same key are never reconciled

loadedInsights and loadedError are stored independently and only compared against currentInsightsKey. The effect re-runs whenever entries changes (same key), so: (a) a failed fetch followed by a successful refetch leaves loadedError set, so the backend-error banner (and insightsError !== null) stays visible next to valid backend data; (b) a successful fetch followed by a failed refetch keeps the old loadedInsights, so backendInsights stays non-null and stale backend numbers are shown alongside the error instead of the fallback. Clear the opposite state when each request settles, e.g. setLoadedError(null) here and setLoadedInsights(null) in the .catch handler (async callbacks, so this does not violate the no-sync-setState-in-effect rule), or store a single {key, data | error} result.

@debba debba left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for keying the Insights response by connection and filters, and for adding the pending-connection regression. The two targeted suites pass on this head with 42 tests.

There is still a request-state issue when the same selection is fetched again. A failed request stores loadedError, but a later successful request only updates loadedInsights and never clears that error. For example, open Insights for A and let its request fail, switch to B, then return to A and let the new request succeed. Both stored entries now match A's key, so the old error alert remains visible alongside the successful data. Conversely, a later failure does not clear previously loaded data for that same key.

Please keep the result and error in one request state, or clear the opposite state when a request settles, and add a retry/switch-back regression covering failure followed by success and success followed by failure. This matters because the current warning tells users that the backend failed even after the current request succeeded.

I have not run the real-app backend execution-log path on this head.

@markankamaransheogarat-sys

Copy link
Copy Markdown
Contributor Author

@debba Thanks for the review!

  • Unified Insights Request State: Replaced the separate loadedInsights and loadedError states with a unified insightsResult state tagged by request key. When a subsequent request settles with success, any prior error for that key/selection is immediately cleared; conversely, when a subsequent request fails, prior loaded data is cleared.
  • Loading & Warning Invariants: The error alert only renders when the active selection has an error result, eliminating the issue where a stale error alert lingered alongside successful fresh data.
  • Regression Tests: Added clears previous error on subsequent success and clears previous success on subsequent error covering both failure → switch/retry → success and success → switch/retry → failure sequences.

@NewtTheWolf NewtTheWolf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for replacing the independent cached data/error states with one keyed result. I checked the A-to-B-to-A retry handling on 11f5217. The 43 affected frontend tests, typecheck and all 2,389 Rust library tests pass locally; the previous error can no longer coexist with a successful response for that request key.

Please rebase onto current main before the final merge check. There are conflicts in query_history.rs, the desktop command registry and the locale files. Keep the new single-result state and retry regressions while resolving those conflicts. This remains open for that integration update.

@markankamaransheogarat-sys
markankamaransheogarat-sys force-pushed the feat/query-history-insights-1413 branch from 11f5217 to 0a1f229 Compare November 10, 2026 17:05
@markankamaransheogarat-sys

Copy link
Copy Markdown
Contributor Author

@NewtTheWolf Rebased onto current main and resolved the conflicts in query_history.rs, the desktop command registry, and the locale files while preserving the unified single-result state and retry regression coverage. All 43 targeted frontend tests, typecheck, Prettier, and CI workflows pass cleanly.

@NewtTheWolf NewtTheWolf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for preserving the unified Insights result during the rebase. On 0a1f229d, the query-history implementation and request-state code are byte-for-byte unchanged from the corrected 11f5217b revision. The A-to-B-to-A retry regression remains covered. The 43 affected frontend tests, typecheck, lint, production build and desktop-free cargo check pass; the full Rust library suite passes serially with 2,528 passed and 19 ignored.

The code correction still looks good, but the branch conflicts with the main that has advanced during this rerun. Please integrate current main again, preserving the single keyed data/error result and the current desktop command registry and locale entries. I am requesting changes for that remaining integration blocker, rather than treating the earlier green CI as evidence for a conflict resolution that has not been reviewed. I have not repeated the native execution-log flow in this pass.

@debba debba left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for replacing the separate data/error caches with one keyed result. A successful request now replaces the old error, and a failure replaces old data. The switch-back regression exercises both directions; the 43 affected frontend tests and typecheck pass on this head.

That addresses my previous request. One integration blocker remains: the branch conflicts with current main in packages/web-ui/src/components/layout/sidebar/QueryHistorySection.tsx and its component test. I confirmed both conflicts with a local merge-tree check.

Please rebase and resolve those two files while preserving the unified result state and the retry regression, then rerun the affected tests. I'm requesting changes for the integration conflict, not for the corrected request handling.

@markankamaransheogarat-sys

Copy link
Copy Markdown
Contributor Author

@debba Rebased cleanly onto latest main, resolving the conflicts in QueryHistorySection.tsx, QueryHistorySection.test.tsx, and the locale files while preserving the unified result state, the searchErrors clear on filter reset, and all regression tests. Verified QueryHistorySection.test.tsx (42 passed), queryHistoryInsights.test.ts (7 passed), and tsc --noEmit.

@markankamaransheogarat-sys
markankamaransheogarat-sys force-pushed the feat/query-history-insights-1413 branch from 0a1f229 to df0eba8 Compare November 14, 2026 20:16

This branch has not been deployed

No deployments
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.

[Feat]: Query history: show aggregated performance insights and regressions

3 participants