From 4ab567d56786ebb7d21753777a47c21530cfa5ed Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 18 Sep 2025 15:16:22 +0100 Subject: [PATCH 1/4] fix: Enhance pulse animation effect for loading state --- packages/app/src/HDXMultiSeriesTimeChart.tsx | 330 ++++++++++--------- packages/app/styles/app.scss | 11 +- 2 files changed, 178 insertions(+), 163 deletions(-) diff --git a/packages/app/src/HDXMultiSeriesTimeChart.tsx b/packages/app/src/HDXMultiSeriesTimeChart.tsx index a6ff8b802..299994d18 100644 --- a/packages/app/src/HDXMultiSeriesTimeChart.tsx +++ b/packages/app/src/HDXMultiSeriesTimeChart.tsx @@ -31,6 +31,7 @@ import { } from 'recharts'; import { DisplayType } from '@hyperdx/common-utils/dist/types'; import { Popover } from '@mantine/core'; +import { Text } from '@mantine/core'; import { notifications } from '@mantine/notifications'; import type { NumberFormat } from '@/types'; @@ -371,173 +372,186 @@ export const MemoChart = memo(function MemoChart({ const [highlightEnd, setHighlightEnd] = useState(); return ( - { - sizeRef.current = [width ?? 1, height ?? 1]; - }} - className={isLoading ? 'effect-pulse' : ''} - > - setIsHovered(true)} - onMouseLeave={e => { - setIsHovered(false); - - setHighlightStart(undefined); - setHighlightEnd(undefined); +
+ { + sizeRef.current = [width ?? 1, height ?? 1]; }} - onMouseDown={e => e != null && setHighlightStart(e.activeLabel)} - onMouseMove={e => { - setIsHovered(true); + className={isLoading ? 'effect-pulse' : ''} + > + setIsHovered(true)} + onMouseLeave={e => { + setIsHovered(false); - if (highlightStart != null) { - setHighlightEnd(e.activeLabel); - setIsClickActive(undefined); // Clear out any click state as we're highlighting - } - }} - onMouseUp={e => { - if (e?.activeLabel != null && highlightStart === e.activeLabel) { - // If it's just a click, don't zoom setHighlightStart(undefined); setHighlightEnd(undefined); - } - if (highlightStart != null && highlightEnd != null) { - try { - onTimeRangeSelect?.( - new Date( - Number.parseInt( - highlightStart <= highlightEnd - ? highlightStart - : highlightEnd, - ) * 1000, - ), - new Date( - Number.parseInt( - highlightEnd >= highlightStart - ? highlightEnd - : highlightStart, - ) * 1000, - ), - ); - } catch (e) { - console.error('failed to highlight range', e); + }} + onMouseDown={e => e != null && setHighlightStart(e.activeLabel)} + onMouseMove={e => { + setIsHovered(true); + + if (highlightStart != null) { + setHighlightEnd(e.activeLabel); + setIsClickActive(undefined); // Clear out any click state as we're highlighting + } + }} + onMouseUp={e => { + if (e?.activeLabel != null && highlightStart === e.activeLabel) { + // If it's just a click, don't zoom + setHighlightStart(undefined); + setHighlightEnd(undefined); + } + if (highlightStart != null && highlightEnd != null) { + try { + onTimeRangeSelect?.( + new Date( + Number.parseInt( + highlightStart <= highlightEnd + ? highlightStart + : highlightEnd, + ) * 1000, + ), + new Date( + Number.parseInt( + highlightEnd >= highlightStart + ? highlightEnd + : highlightStart, + ) * 1000, + ), + ); + } catch (e) { + console.error('failed to highlight range', e); + } + setHighlightStart(undefined); + setHighlightEnd(undefined); + } + }} + onClick={(state, e) => { + if ( + state != null && + state.chartX != null && + state.chartY != null && + state.activeLabel != null && + // If we didn't drag and highlight yet + highlightStart == null + ) { + setIsClickActive({ + x: state.chartX, + y: state.chartY, + activeLabel: state.activeLabel, + xPerc: state.chartX / sizeRef.current[0], + yPerc: state.chartY / sizeRef.current[1], + }); + } else { + // We clicked on the chart but outside of a line + setIsClickActive(undefined); } - setHighlightStart(undefined); - setHighlightEnd(undefined); - } - }} - onClick={(state, e) => { - if ( - state != null && - state.chartX != null && - state.chartY != null && - state.activeLabel != null && - // If we didn't drag and highlight yet - highlightStart == null - ) { - setIsClickActive({ - x: state.chartX, - y: state.chartY, - activeLabel: state.activeLabel, - xPerc: state.chartX / sizeRef.current[0], - yPerc: state.chartY / sizeRef.current[1], - }); - } else { - // We clicked on the chart but outside of a line - setIsClickActive(undefined); - } - // TODO: Properly detect clicks outside of the fake tooltip - e.stopPropagation(); - }} - > - - {COLORS.map(c => { - return ( - - - - - ); - })} - - {isHovered && ( - - )} - - - {lines} - } - wrapperStyle={{ - zIndex: 1, + // TODO: Properly detect clicks outside of the fake tooltip + e.stopPropagation(); }} - allowEscapeViewBox={{ y: true }} - /> - {referenceLines} - {highlightStart && highlightEnd ? ( - + + {COLORS.map(c => { + return ( + + + + + ); + })} + + {isHovered && ( + + )} + - ) : null} - {showLegend && ( - } - offset={-100} + - )} - {/** Needs to be at the bottom to prevent re-rendering */} - {isClickActive != null ? ( - - ) : null} - {logReferenceTimestamp != null ? ( - } + wrapperStyle={{ + zIndex: 1, + }} + allowEscapeViewBox={{ y: true }} /> - ) : null} - - + {referenceLines} + {highlightStart && highlightEnd ? ( + + ) : null} + {showLegend && ( + } + offset={-100} + /> + )} + {/** Needs to be at the bottom to prevent re-rendering */} + {isClickActive != null ? ( + + ) : null} + {logReferenceTimestamp != null ? ( + + ) : null} + + + + {isLoading && ( + + Loading... + + )} +
); }); diff --git a/packages/app/styles/app.scss b/packages/app/styles/app.scss index 061ad7d55..8fb8bf910 100644 --- a/packages/app/styles/app.scss +++ b/packages/app/styles/app.scss @@ -661,18 +661,19 @@ body { } .effect-pulse { - animation: pulse 1s infinite; + animation: pulse 1.5s infinite ease-in-out; + filter: grayscale(0.8) opacity(0.5); } @keyframes pulse { 0% { - opacity: 0.7; + opacity: 0.4; } - 20% { - opacity: 1; + 50% { + opacity: 0.8; } 100% { - opacity: 0.7; + opacity: 0.4; } } From 3d72d489c6209576611c4605a2f389f3579f4263 Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Thu, 18 Sep 2025 15:36:17 +0100 Subject: [PATCH 2/4] Add changeset --- .changeset/popular-zoos-applaud.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-zoos-applaud.md diff --git a/.changeset/popular-zoos-applaud.md b/.changeset/popular-zoos-applaud.md new file mode 100644 index 000000000..1926b0f11 --- /dev/null +++ b/.changeset/popular-zoos-applaud.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": minor +--- + +fix: improve the visibility of the search bar chart loading state From 25d16983a585aa21efcf355d1106ec1398d8387a Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Fri, 26 Sep 2025 17:33:52 +0100 Subject: [PATCH 3/4] feat: add isLive prop to DBTimeChart and MemoChart components --- packages/app/src/DBSearchPage.tsx | 2 ++ packages/app/src/HDXMultiSeriesTimeChart.tsx | 6 ++++-- packages/app/src/components/DBTimeChart.tsx | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index 65df342be..82b7124c9 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -1548,6 +1548,7 @@ function DBSearchPage() { showDisplaySwitcher={false} queryKeyPrefix={QUERY_KEY_PREFIX} onTimeRangeSelect={handleTimeRangeSelect} + isLive={isLive} /> )} @@ -1659,6 +1660,7 @@ function DBSearchPage() { showDisplaySwitcher={false} queryKeyPrefix={QUERY_KEY_PREFIX} onTimeRangeSelect={handleTimeRangeSelect} + isLive={isLive} /> )} diff --git a/packages/app/src/HDXMultiSeriesTimeChart.tsx b/packages/app/src/HDXMultiSeriesTimeChart.tsx index 299994d18..f8107c697 100644 --- a/packages/app/src/HDXMultiSeriesTimeChart.tsx +++ b/packages/app/src/HDXMultiSeriesTimeChart.tsx @@ -240,6 +240,7 @@ export const MemoChart = memo(function MemoChart({ timestampKey = 'ts_bucket', onTimeRangeSelect, showLegend = true, + isLive = false, }: { graphResults: any[]; setIsClickActive: (v: any) => void; @@ -256,6 +257,7 @@ export const MemoChart = memo(function MemoChart({ timestampKey?: string; onTimeRangeSelect?: (start: Date, end: Date) => void; showLegend?: boolean; + isLive?: boolean; }) { const _id = useId(); const id = _id.replace(/:/g, ''); @@ -380,7 +382,7 @@ export const MemoChart = memo(function MemoChart({ onResize={(width, height) => { sizeRef.current = [width ?? 1, height ?? 1]; }} - className={isLoading ? 'effect-pulse' : ''} + className={isLoading && !isLive ? 'effect-pulse' : ''} >
- {isLoading && ( + {isLoading && !isLive && ( Date: Fri, 26 Sep 2025 18:08:12 +0100 Subject: [PATCH 4/4] feat: enhance loading state management for live tail mode during filter application --- packages/app/src/DBSearchPage.tsx | 33 ++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index 82b7124c9..47d7fce04 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -822,10 +822,35 @@ function DBSearchPage() { setQueryErrors, ]); - const debouncedSubmit = useDebouncedCallback(onSubmit, 1000); + // Filter loading state management for live tail mode + // This allows showing loading animations when applying filters during live tail, + // without kicking the user out of live tail mode (which would show "Resume Live Tail" button) + const [isFiltering, setIsFiltering] = useState(false); + const filteringTimeoutRef = useRef(null); + + // Clean up timeout on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (filteringTimeoutRef.current) { + clearTimeout(filteringTimeoutRef.current); + } + }; + }, []); + + const debouncedSubmit = useDebouncedCallback(() => { + onSubmit(); + // Clear filtering state after the submit completes to restore normal live tail behavior + if (filteringTimeoutRef.current) { + clearTimeout(filteringTimeoutRef.current); + } + filteringTimeoutRef.current = setTimeout(() => setIsFiltering(false), 1500); + }, 1000); + const handleSetFilters = useCallback( (filters: Filter[]) => { setValue('filters', filters); + // Set filtering state to show loading animations even during live tail mode + setIsFiltering(true); debouncedSubmit(); }, [debouncedSubmit, setValue], @@ -1548,7 +1573,8 @@ function DBSearchPage() { showDisplaySwitcher={false} queryKeyPrefix={QUERY_KEY_PREFIX} onTimeRangeSelect={handleTimeRangeSelect} - isLive={isLive} + // Pass false when filtering to show loading animations during live tail + isLive={isLive && !isFiltering} /> )} @@ -1660,7 +1686,8 @@ function DBSearchPage() { showDisplaySwitcher={false} queryKeyPrefix={QUERY_KEY_PREFIX} onTimeRangeSelect={handleTimeRangeSelect} - isLive={isLive} + // Pass false when filtering to show loading animations during live tail + isLive={isLive && !isFiltering} /> )}