Show elapsed time and Generated SQL for search timeline view#2384
Show elapsed time and Generated SQL for search timeline view#2384vinzee wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: eade7be The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@vinzee is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
Deep Review🔴 P0/P1 -- must fix
🟡 P2 -- recommended
🔵 P3 nitpicks (11)
Reviewers (10): correctness, testing, maintainability, project-standards, agent-native, learnings-researcher, adversarial, kieran-typescript, julik-frontend-races, previous-comments. Testing gaps:
|
e1c22e2 to
37c6108
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
karl-power
left a comment
There was a problem hiding this comment.
Thanks @vinzee - nice features. Can you check the P0/P1 issues from the deep review and update if needed?
I also noticed this flicker on the generated SQL after each poll
Untitled.mov
Please also add a changeset when ready.
Shows scanned row count and elapsed query time inline on the search page. Adds a code icon button that opens a modal with the formatted generated SQL.
802c937 to
eade7be
Compare
Greptile SummaryThis PR adds elapsed query time and scanned-row count display to the search timeline view, and surfaces a "Show Generated SQL" button that opens a modal with the formatted ClickHouse SQL for the histogram config. A new
Confidence Score: 4/5Safe to merge; the changes are UI-only and additive with no data-mutation paths. The core hook logic is well-designed and the test coverage is thorough. Two non-blocking quality issues exist: elapsed time is silently hidden when the auxiliary explain query fails (even though packages/app/src/DBSearchPage.tsx — specifically the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([isAnyQueryFetching changes]) --> B{isAnyQueryFetching?}
B -- yes --> C{isLive?}
C -- yes --> D[setCompletedSearch null\nstart timer suppressed]
C -- no --> E{searchStartTimeRef\n== null?}
E -- yes --> F[stamp searchStartTimeRef\n= performance.now]
E -- no --> G[timer already running]
F --> H[setCompletedSearch null]
G --> H
B -- no --> I{searchStartTimeRef\n!= null?}
I -- yes --> J[snapshot latency_ms + source_id\ninto completedSearch state\nclear searchStartTimeRef]
I -- no --> K[no-op]
J --> L[second effect fires\nHyperDX.addAction search executed]
L --> M[return searchElapsedMs\nto SearchNumRows]
M --> N{hasData from\nuseExplainQuery?}
N -- yes --> O[show Scanned Rows\nElapsed Time\nSQL icon button]
N -- no --> P[show loading or\nempty string only]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A([isAnyQueryFetching changes]) --> B{isAnyQueryFetching?}
B -- yes --> C{isLive?}
C -- yes --> D[setCompletedSearch null\nstart timer suppressed]
C -- no --> E{searchStartTimeRef\n== null?}
E -- yes --> F[stamp searchStartTimeRef\n= performance.now]
E -- no --> G[timer already running]
F --> H[setCompletedSearch null]
G --> H
B -- no --> I{searchStartTimeRef\n!= null?}
I -- yes --> J[snapshot latency_ms + source_id\ninto completedSearch state\nclear searchStartTimeRef]
I -- no --> K[no-op]
J --> L[second effect fires\nHyperDX.addAction search executed]
L --> M[return searchElapsedMs\nto SearchNumRows]
M --> N{hasData from\nuseExplainQuery?}
N -- yes --> O[show Scanned Rows\nElapsed Time\nSQL icon button]
N -- no --> P[show loading or\nempty string only]
Reviews (1): Last reviewed commit: "Show elapsed time and Generated SQL moda..." | Re-trigger Greptile |
| | | ||
| </Text> | ||
| <Text size="xs"> | ||
| {isSearching | ||
| ? 'Elapsed Time: ...' | ||
| : `Elapsed Time: ${formatDurationMs(searchElapsedMs!)}`} | ||
| </Text> | ||
| </> | ||
| )} | ||
| {hasData && ( | ||
| <Tooltip label="Show Generated SQL" position="top"> | ||
| <ActionIcon | ||
| variant="subtle" | ||
| size="sm" | ||
| color="gray" | ||
| onClick={openStats} | ||
| aria-label="Show Generated SQL" | ||
| > | ||
| <IconCode size={16} /> | ||
| </ActionIcon> | ||
| </Tooltip> | ||
| )} |
There was a problem hiding this comment.
Elapsed time hidden on explain-query failure
searchElapsedMs is produced by useSearchTelemetry and is completely independent of the explain query, yet it is gated behind hasData which requires !isLoading && !error && numRows != null. If the explain query times out, returns an empty array, or yields a row without the rows field, the elapsed time (and the SQL icon) are silently omitted even though the main search completed normally. A network hiccup on the auxiliary EXPLAIN call would leave the user with no timing feedback at all.
| useEffect(() => { | ||
| if (isAnyQueryFetching) { | ||
| // Skip live-tail background ticks entirely. | ||
| // Only anchor the start once per user-initiated search cycle so that | ||
| // brief dips to zero between staggered queries don't reset the clock. | ||
| if (!isLive && searchStartTimeRef.current == null) { | ||
| searchStartTimeRef.current = performance.now(); | ||
| } | ||
| setCompletedSearch(null); | ||
| } else if (searchStartTimeRef.current != null) { | ||
| setCompletedSearch({ | ||
| latency_ms: Math.round(performance.now() - searchStartTimeRef.current), | ||
| source_id: sourceId ?? '', | ||
| }); | ||
| searchStartTimeRef.current = null; | ||
| } | ||
| }, [isAnyQueryFetching, isLive, sourceId]); |
There was a problem hiding this comment.
Timer stamps too late when
isLive changes from true → false mid-query
isLive is in the dependency array. If a live-tail background refresh is already in-flight (isAnyQueryFetching = true, searchStartTimeRef.current = null because isLive was true) and the user disables live mode during that tick, the effect re-runs with isAnyQueryFetching = true, isLive = false. The guard !isLive && searchStartTimeRef.current == null evaluates to true, so the timer is stamped at the moment isLive changes — not when the query actually started. The elapsed time shown on completion will be shorter than the true query duration.
Summary
Shows scanned row count and elapsed query time inline on the search page. Adds a code icon button that opens a modal with the formatted generated SQL (similar to the modal in results table).
Screenshots or video
Elapsed time with the

Show Generated SQLbutton:Modal showing SQL for the timeline view:

How to test on Vercel preview
Preview routes:
Steps:
References