From 9fbae9b2f4df415b27592a9cc489425df7f1bda0 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Mon, 15 Sep 2025 15:39:52 -0600 Subject: [PATCH 1/7] core search sidebar component, various app fixes --- packages/app/src/ClickhousePage.tsx | 19 +- packages/app/src/DBDashboardPage.tsx | 28 +-- packages/app/src/DBSearchPage.tsx | 72 +++----- packages/app/src/KubernetesDashboardPage.tsx | 33 +--- .../app/src/NamespaceDetailsSidePanel.tsx | 11 +- packages/app/src/NodeDetailsSidePanel.tsx | 11 +- packages/app/src/PodDetailsSidePanel.tsx | 11 +- .../app/src/components/ContextSidePanel.tsx | 3 +- .../src/components/DBEditTimeChartForm.tsx | 15 +- .../app/src/components/DBRowSidePanel.tsx | 100 +++++------ packages/app/src/components/DBRowTable.tsx | 40 +++-- .../components/DBSqlRowTableWithSidebar.tsx | 162 ++++++++++++++++++ .../app/src/components/ExpandableRowTable.tsx | 102 +++-------- .../app/src/components/PatternSidePanel.tsx | 2 +- packages/app/src/components/PatternTable.tsx | 2 +- .../ServiceDashboardSlowestEventsTile.tsx | 35 +--- 16 files changed, 335 insertions(+), 311 deletions(-) create mode 100644 packages/app/src/components/DBSqlRowTableWithSidebar.tsx diff --git a/packages/app/src/ClickhousePage.tsx b/packages/app/src/ClickhousePage.tsx index 84fcca01f6..3fd81c93cb 100644 --- a/packages/app/src/ClickhousePage.tsx +++ b/packages/app/src/ClickhousePage.tsx @@ -7,6 +7,8 @@ import { useQueryStates, } from 'nuqs'; import { useForm } from 'react-hook-form'; +import { sql } from '@codemirror/lang-sql'; +import { format as formatSql } from '@hyperdx/common-utils/dist/sqlFormatter'; import { DisplayType } from '@hyperdx/common-utils/dist/types'; import { Box, @@ -19,6 +21,7 @@ import { Tabs, Text, } from '@mantine/core'; +import ReactCodeMirror from '@uiw/react-codemirror'; import { ConnectionSelectControlled } from '@/components/ConnectionSelect'; import { DBTimeChart } from '@/components/DBTimeChart'; @@ -702,9 +705,18 @@ function ClickhousePage() { Slowest Queries {}} - showExpandButton={false} + renderRowDetails={row => { + return ( + + ); + }} config={{ select: `event_time, query_kind, read_rows, @@ -735,7 +747,6 @@ function ClickhousePage() { ], limit: { limit: 100 }, }} - onScroll={() => {}} /> diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 7c501fa6d7..bf96d8abeb 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -49,12 +49,10 @@ import { } from '@mantine/core'; import { useHover, usePrevious } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; -import { useQueryClient } from '@tanstack/react-query'; import { ContactSupportText } from '@/components/ContactSupportText'; import EditTimeChartForm from '@/components/DBEditTimeChartForm'; import DBNumberChart from '@/components/DBNumberChart'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import DBTableChart from '@/components/DBTableChart'; import { DBTimeChart } from '@/components/DBTimeChart'; import { SQLInlineEditorControlled } from '@/components/SQLInlineEditor'; @@ -66,11 +64,10 @@ import { useDeleteDashboard, } from '@/dashboard'; -import DBRowSidePanel from './components/DBRowSidePanel'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import OnboardingModal from './components/OnboardingModal'; import { Tags } from './components/Tags'; import { useDashboardRefresh } from './hooks/useDashboardRefresh'; -import { useAllFields } from './hooks/useMetadata'; import api from './api'; import { DEFAULT_CHART_CONFIG } from './ChartUtils'; import { IS_LOCAL_MODE } from './config'; @@ -197,17 +194,6 @@ const Tile = forwardRef( const [hovered, setHovered] = useState(false); - // Search tile - const [rowId, setRowId] = useQueryState('rowWhere'); - const [_, setRowSource] = useQueryState('rowSource'); - const handleRowExpandClick = useCallback( - (rowWhere: string) => { - setRowId(rowWhere); - setRowSource(chart.config.source); - }, - [chart.config.source, setRowId, setRowSource], - ); - const alert = chart.config.alert; const alertIndicatorColor = useMemo(() => { if (!alert) { @@ -346,7 +332,7 @@ const Tile = forwardRef( )} {queriedConfig?.displayType === DisplayType.Search && ( - {}} isLive={false} queryKeyPrefix={'search'} /> @@ -1063,13 +1046,6 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { > + Add New Tile - {rowId && rowSidePanelSource && ( - - )} ); } diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx index e0e51cc270..c6cfa037a0 100644 --- a/packages/app/src/DBSearchPage.tsx +++ b/packages/app/src/DBSearchPage.tsx @@ -58,13 +58,9 @@ import { notifications } from '@mantine/notifications'; import { useIsFetching } from '@tanstack/react-query'; import CodeMirror from '@uiw/react-codemirror'; -import { useTimeChartSettings } from '@/ChartUtils'; import { ContactSupportText } from '@/components/ContactSupportText'; import DBDeltaChart from '@/components/DBDeltaChart'; import DBHeatmapChart from '@/components/DBHeatmapChart'; -import DBRowSidePanel from '@/components/DBRowSidePanel'; -import { RowSidePanelContext } from '@/components/DBRowSidePanel'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import { DBSearchPageFilters } from '@/components/DBSearchPageFilters'; import { DBTimeChart } from '@/components/DBTimeChart'; import { ErrorBoundary } from '@/components/ErrorBoundary'; @@ -103,6 +99,7 @@ import { parseTimeQuery, useNewTimeQuery } from '@/timeQuery'; import { QUERY_LOCAL_STORAGE, useLocalStorage, usePrevious } from '@/utils'; import { SQLPreview } from './components/ChartSQLPreview'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import PatternTable from './components/PatternTable'; import { useTableMetadata } from './hooks/useMetadata'; import { useSqlSuggestions } from './hooks/useSqlSuggestions'; @@ -688,7 +685,6 @@ function DBSearchPage() { const inputSourceObj = inputSourceObjs?.find(s => s.id === inputSource); const defaultOrderBy = useDefaultOrderBy(inputSource); - const [rowId, setRowId] = useQueryState('rowWhere'); const [displayedTimeInputValue, setDisplayedTimeInputValue] = useState('Live Tail'); @@ -872,13 +868,9 @@ function DBSearchPage() { [isLive, setIsLive], ); - const onRowExpandClick = useCallback( - (rowWhere: string) => { - setIsLive(false); - setRowId(rowWhere); - }, - [setRowId, setIsLive], - ); + const onSidebarOpen = useCallback(() => { + setIsLive(false); + }, [setIsLive]); const [modelFormExpanded, setModelFormExpanded] = useState(false); // Used in local mode const [saveSearchModalState, setSaveSearchModalState] = useState< @@ -1002,7 +994,7 @@ function DBSearchPage() { const { data: me } = api.useMe(); // Callback to handle when rows are expanded - kick user out of live tail - const handleExpandedRowsChange = useCallback( + const onExpandedRowsChange = useCallback( (hasExpandedRows: boolean) => { if (hasExpandedRows && isLive) { setIsLive(false); @@ -1454,25 +1446,6 @@ function DBSearchPage() { - - {searchedSource && ( - setRowId(null)} - /> - )} - {searchedConfig != null && searchedSource != null && ( )} {chartConfig && + searchedConfig.source && dbSqlRowTableConfig && analysisMode === 'results' && ( - - - + config={dbSqlRowTableConfig} + sourceId={searchedConfig.source} + onSidebarOpen={onSidebarOpen} + onExpandedRowsChange={onExpandedRowsChange} + enabled={isReady} + isLive={isLive ?? true} + queryKeyPrefix={QUERY_KEY_PREFIX} + onScroll={onTableScroll} + onError={handleTableError} + denoiseResults={denoiseResults} + collapseAllRows={collapseAllRows} + /> )} )} diff --git a/packages/app/src/KubernetesDashboardPage.tsx b/packages/app/src/KubernetesDashboardPage.tsx index 7eac5bc8dd..95af2ce963 100644 --- a/packages/app/src/KubernetesDashboardPage.tsx +++ b/packages/app/src/KubernetesDashboardPage.tsx @@ -38,8 +38,7 @@ import { import { TimePicker } from '@/components/TimePicker'; import { ConnectionSelectControlled } from './components/ConnectionSelect'; -import DBRowSidePanel from './components/DBRowSidePanel'; -import { DBSqlRowTable } from './components/DBRowTable'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import { DBTimeChart } from './components/DBTimeChart'; import { FormatPodStatus } from './components/KubeComponents'; import { KubernetesFilters } from './components/KubernetesFilters'; @@ -830,24 +829,6 @@ function KubernetesDashboardPage() { [_searchQuery, setSearchQuery], ); - // Row details side panel - const [rowId, setRowId] = useQueryState('rowWhere'); - const [rowSource, setRowSource] = useQueryState('rowSource'); - const { data: rowSidePanelSource } = useSource({ id: rowSource || '' }); - - const handleSidePanelClose = React.useCallback(() => { - setRowId(null); - setRowSource(null); - }, [setRowId, setRowSource]); - - const handleRowExpandClick = React.useCallback( - (rowWhere: string) => { - setRowId(rowWhere); - setRowSource(logSource?.id ?? null); - }, - [logSource?.id, setRowId, setRowSource], - ); - return ( @@ -869,13 +850,6 @@ function KubernetesDashboardPage() { logSource={logSource} /> )} - {rowId && rowSidePanelSource && ( - - )} @@ -1044,7 +1018,7 @@ function KubernetesDashboardPage() { {logSource && ( - {}} /> )} diff --git a/packages/app/src/NamespaceDetailsSidePanel.tsx b/packages/app/src/NamespaceDetailsSidePanel.tsx index ace7e01849..b43c0020b7 100644 --- a/packages/app/src/NamespaceDetailsSidePanel.tsx +++ b/packages/app/src/NamespaceDetailsSidePanel.tsx @@ -22,7 +22,6 @@ import { K8S_CPU_PERCENTAGE_NUMBER_FORMAT, K8S_MEM_NUMBER_FORMAT, } from '@/ChartUtils'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import { DBTimeChart } from '@/components/DBTimeChart'; import { DrawerBody, DrawerHeader } from '@/components/DrawerUtils'; import { useQueriedChartConfig } from '@/hooks/useChartConfig'; @@ -31,6 +30,7 @@ import { getEventBody } from '@/source'; import { parseTimeQuery, useTimeQuery } from '@/timeQuery'; import { useZIndex, ZIndexContext } from '@/zIndex'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import { useGetKeyValues, useTableMetadata } from './hooks/useMetadata'; import styles from '../styles/LogSidePanel.module.scss'; @@ -182,7 +182,10 @@ function NamespaceLogs({ - {}} - highlightedLineId={undefined} isLive={false} queryKeyPrefix="k8s-dashboard-namespace-logs" - onScroll={() => {}} - showExpandButton={false} /> diff --git a/packages/app/src/NodeDetailsSidePanel.tsx b/packages/app/src/NodeDetailsSidePanel.tsx index 9fa2288a3d..5d108a1216 100644 --- a/packages/app/src/NodeDetailsSidePanel.tsx +++ b/packages/app/src/NodeDetailsSidePanel.tsx @@ -25,7 +25,6 @@ import { K8S_CPU_PERCENTAGE_NUMBER_FORMAT, K8S_MEM_NUMBER_FORMAT, } from '@/ChartUtils'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import { DBTimeChart } from '@/components/DBTimeChart'; import { DrawerBody, DrawerHeader } from '@/components/DrawerUtils'; import { InfraPodsStatusTable } from '@/KubernetesDashboardPage'; @@ -34,6 +33,7 @@ import { parseTimeQuery, useTimeQuery } from '@/timeQuery'; import { formatUptime } from '@/utils'; import { useZIndex, ZIndexContext } from '@/zIndex'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import { useQueriedChartConfig } from './hooks/useChartConfig'; import { useGetKeyValues, useTableMetadata } from './hooks/useMetadata'; @@ -201,7 +201,10 @@ function NodeLogs({ - {}} - highlightedLineId={undefined} isLive={false} queryKeyPrefix="k8s-dashboard-node-logs" - showExpandButton={false} - onScroll={() => {}} /> diff --git a/packages/app/src/PodDetailsSidePanel.tsx b/packages/app/src/PodDetailsSidePanel.tsx index 60eac8a6ad..435ee2f03c 100644 --- a/packages/app/src/PodDetailsSidePanel.tsx +++ b/packages/app/src/PodDetailsSidePanel.tsx @@ -22,13 +22,13 @@ import { K8S_MEM_NUMBER_FORMAT, } from '@/ChartUtils'; import DBRowSidePanel from '@/components/DBRowSidePanel'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import { DBTimeChart } from '@/components/DBTimeChart'; import { DrawerBody, DrawerHeader } from '@/components/DrawerUtils'; import { KubeTimeline, useV2LogBatch } from '@/components/KubeComponents'; import { parseTimeQuery, useTimeQuery } from '@/timeQuery'; import { useZIndex, ZIndexContext } from '@/zIndex'; +import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; import { useGetKeyValues, useTableMetadata } from './hooks/useMetadata'; import { getEventBody } from './source'; @@ -144,7 +144,7 @@ function PodLogs({ }) { const [resultType, setResultType] = React.useState<'all' | 'error'>('all'); - const _where = where + (resultType === 'error' ? ' Severity:err' : ''); + const _where = resultType === 'error' ? ' Severity:err' : ''; // Create a properly typed config object for DBSqlRowTable const tableConfig = React.useMemo(() => { @@ -206,14 +206,13 @@ function PodLogs({ - {}} /> diff --git a/packages/app/src/components/ContextSidePanel.tsx b/packages/app/src/components/ContextSidePanel.tsx index b8b39bb502..2127435059 100644 --- a/packages/app/src/components/ContextSidePanel.tsx +++ b/packages/app/src/components/ContextSidePanel.tsx @@ -319,10 +319,11 @@ export default function ContextSubpanel({ diff --git a/packages/app/src/components/DBEditTimeChartForm.tsx b/packages/app/src/components/DBEditTimeChartForm.tsx index 133c1b305e..f26f1d551c 100644 --- a/packages/app/src/components/DBEditTimeChartForm.tsx +++ b/packages/app/src/components/DBEditTimeChartForm.tsx @@ -50,7 +50,6 @@ import { import { AGG_FNS } from '@/ChartUtils'; import { AlertChannelForm, getAlertReferenceLines } from '@/components/Alerts'; import ChartSQLPreview from '@/components/ChartSQLPreview'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import DBTableChart from '@/components/DBTableChart'; import { DBTimeChart } from '@/components/DBTimeChart'; import { SQLInlineEditorControlled } from '@/components/SQLInlineEditor'; @@ -75,6 +74,7 @@ import HDXMarkdownChart from '../HDXMarkdownChart'; import { AggFnSelectControlled } from './AggFnSelect'; import DBNumberChart from './DBNumberChart'; +import DBSqlRowTableWithSideBar from './DBSqlRowTableWithSidebar'; import { CheckBoxControlled, InputControlled, @@ -979,7 +979,9 @@ export default function EditTimeChartForm({ className="flex-grow-1 d-flex flex-column" style={{ height: 400 }} > - {}} - highlightedLineId={undefined} enabled isLive={false} - showExpandButton={false} queryKeyPrefix={'search'} - onScroll={() => {}} /> )} @@ -1029,13 +1027,12 @@ export default function EditTimeChartForm({ className="flex-grow-1 d-flex flex-column" style={{ height: 400 }} > - )} diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index b5bd6028a3..0bd4ba99ce 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -15,7 +15,7 @@ import { useHotkeys } from 'react-hotkeys-hook'; import Drawer from 'react-modern-drawer'; import { TSource } from '@hyperdx/common-utils/dist/types'; import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types'; -import { Box, Stack } from '@mantine/core'; +import { Box, OptionalPortal, Stack } from '@mantine/core'; import { useClickOutside } from '@mantine/hooks'; import DBRowSidePanelHeader, { @@ -39,7 +39,7 @@ import DBTracePanel from './DBTracePanel'; import 'react-modern-drawer/dist/index.css'; import styles from '@/../styles/LogSidePanel.module.scss'; -export const RowSidePanelContext = createContext<{ +export type RowSidePanelContextProps = { onPropertyAddClick?: (keyPath: string, value: string) => void; generateSearchUrl?: ({ where, @@ -59,7 +59,9 @@ export const RowSidePanelContext = createContext<{ dbSqlRowTableConfig?: ChartConfigWithDateRange; isChildModalOpen?: boolean; setChildModalOpen?: (open: boolean) => void; -}>({}); +}; + +export const RowSidePanelContext = createContext({}); enum Tab { Overview = 'overview', @@ -148,7 +150,7 @@ const DBRowSidePanel = ({ : Tab.Parsed; const [queryTab, setQueryTab] = useQueryState( - 'tab', + 'sidePanelTab', parseAsStringEnum(Object.values(Tab)).withDefault(defaultTab), ); @@ -484,49 +486,51 @@ export default function DBRowSidePanelErrorBoundary({ }, ['mouseup', 'touchend']); return ( - { - if (!subDrawerOpen) { - _onClose(); - } - }} - direction="right" - size={`${width}vw`} - zIndex={drawerZIndex} - enableOverlay={subDrawerOpen} - > - -
- - - ( - -
- An error occurred while rendering this event. -
- -
- {error?.error?.message} -
-
- )} - > - -
-
-
-
+ + { + if (!subDrawerOpen) { + _onClose(); + } + }} + direction="right" + size={`${width}vw`} + zIndex={drawerZIndex} + enableOverlay={subDrawerOpen} + > + +
+ + + ( + +
+ An error occurred while rendering this event. +
+ +
+ {error?.error?.message} +
+
+ )} + > + +
+
+
+
+
); } diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 32bc313d69..face3f1543 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -273,7 +273,7 @@ const SqlModal = ({ }; export const RawLogTable = memo( - ({ + = Record>({ tableId, displayedColumns, fetchNextPage, @@ -285,7 +285,7 @@ export const RawLogTable = memo( generateRowId, onInstructionsClick, // onPropertySearchClick, - onRowExpandClick, + onRowDetailsClick, onScroll, onSettingsClick, onShowPatternsClick, @@ -300,6 +300,7 @@ export const RawLogTable = memo( loadingDate, config, onChildModalOpen, + renderRowDetails, source, onExpandedRowsChange, collapseAllRows, @@ -309,11 +310,11 @@ export const RawLogTable = memo( displayedColumns: string[]; onSettingsClick?: () => void; onInstructionsClick?: () => void; - rows: Record[]; + rows: R[]; isLoading: boolean; fetchNextPage: (options?: FetchNextPageOptions | undefined) => any; - onRowExpandClick: (row: Record) => void; - generateRowId: (row: Record) => string; + onRowDetailsClick: (row: R) => void; + generateRowId: (row: R) => string; // onPropertySearchClick: ( // name: string, // value: string | number | boolean, @@ -339,6 +340,7 @@ export const RawLogTable = memo( onExpandedRowsChange?: (hasExpandedRows: boolean) => void; collapseAllRows?: boolean; showExpandButton?: boolean; + renderRowDetails?: (row: R) => React.ReactNode; }) => { const generateRowMatcher = generateRowId; @@ -363,9 +365,9 @@ export const RawLogTable = memo( const _onRowExpandClick = useCallback( ({ __hyperdx_id, ...row }: Record) => { - onRowExpandClick(row); + onRowDetailsClick?.(row as unknown as R); }, - [onRowExpandClick], + [onRowDetailsClick], ); const { width } = useWindowSize(); @@ -868,7 +870,12 @@ export const RawLogTable = memo( rowId={rowId} measureElement={rowVirtualizer.measureElement} virtualIndex={virtualRow.index} - /> + > + {renderRowDetails?.({ + id: rowId, + ...row.original, + })} + )} ); @@ -1083,7 +1090,7 @@ function DBSqlRowTableComponent({ config, sourceId, onError, - onRowExpandClick, + onRowDetailsClick, highlightedLineId, enabled = true, isLive = false, @@ -1094,14 +1101,16 @@ function DBSqlRowTableComponent({ onExpandedRowsChange, collapseAllRows, showExpandButton = true, + renderRowDetails, }: { config: ChartConfigWithDateRange; sourceId?: string; - onRowExpandClick?: (where: string) => void; - highlightedLineId: string | undefined; + onRowDetailsClick?: (where: string) => void; + highlightedLineId?: string; queryKeyPrefix?: string; enabled?: boolean; isLive?: boolean; + renderRowDetails?: (r: { [key: string]: unknown }) => React.ReactNode; onScroll?: (scrollTop: number) => void; onError?: (error: Error | ClickHouseQueryError) => void; denoiseResults?: boolean; @@ -1172,11 +1181,11 @@ function DBSqlRowTableComponent({ const getRowWhere = useRowWhere({ meta: data?.meta, aliasMap }); - const _onRowExpandClick = useCallback( + const _onRowDetailsClick = useCallback( (row: Record) => { - return onRowExpandClick?.(getRowWhere(row)); + return onRowDetailsClick?.(getRowWhere(row)); }, - [onRowExpandClick, getRowWhere], + [onRowDetailsClick, getRowWhere], ); useEffect(() => { @@ -1287,11 +1296,12 @@ function DBSqlRowTableComponent({ displayedColumns={columns} highlightedLineId={highlightedLineId} rows={denoiseResults ? (denoisedRows?.data ?? []) : processedRows} + renderRowDetails={renderRowDetails} isLoading={isLoading} fetchNextPage={fetchNextPage} // onPropertySearchClick={onPropertySearchClick} hasNextPage={hasNextPage} - onRowExpandClick={_onRowExpandClick} + onRowDetailsClick={_onRowDetailsClick} onScroll={onScroll} generateRowId={getRowWhere} isError={isError} diff --git a/packages/app/src/components/DBSqlRowTableWithSidebar.tsx b/packages/app/src/components/DBSqlRowTableWithSidebar.tsx new file mode 100644 index 0000000000..e254b0fb71 --- /dev/null +++ b/packages/app/src/components/DBSqlRowTableWithSidebar.tsx @@ -0,0 +1,162 @@ +import { useCallback } from 'react'; +import { useQueryState } from 'nuqs'; +import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse'; +import { + ChartConfigWithDateRange, + TSource, +} from '@hyperdx/common-utils/dist/types'; + +import { useSource } from '@/source'; +import TabBar from '@/TabBar'; +import { useLocalStorage } from '@/utils'; + +import { RowDataPanel } from './DBRowDataPanel'; +import { RowOverviewPanel } from './DBRowOverviewPanel'; +import DBRowSidePanel, { + RowSidePanelContext, + RowSidePanelContextProps, +} from './DBRowSidePanel'; +import { BreadcrumbEntry } from './DBRowSidePanelHeader'; +import { DBSqlRowTable } from './DBRowTable'; + +interface Props { + sourceId: string; + config: ChartConfigWithDateRange; + onError?: (error: Error | ClickHouseQueryError) => void; + onScroll?: (scrollTop: number) => void; + onSidebarOpen?: (rowId: string) => void; + onExpandedRowsChange?: (hasExpandedRows: boolean) => void; + onPropertyAddClick?: (keyPath: string, value: string) => void; + context?: RowSidePanelContextProps; + enabled?: boolean; + isLive?: boolean; + queryKeyPrefix?: string; + denoiseResults?: boolean; + collapseAllRows?: boolean; + isNestedPanel?: boolean; + breadcrumbPath?: BreadcrumbEntry[]; +} + +export default function DBSqlRowTableWithSideBar({ + sourceId, + config, + onError, + onScroll, + context, + onExpandedRowsChange, + denoiseResults, + collapseAllRows, + isLive, + enabled, + isNestedPanel, + breadcrumbPath, + onSidebarOpen, +}: Props) { + const { data: sourceData } = useSource({ id: sourceId }); + const [rowId, setRowId] = useQueryState('rowWhere'); + const [, setRowSource] = useQueryState('rowSource'); + + const onOpenSidebar = useCallback( + (rowWhere: string) => { + setRowId(rowWhere); + setRowSource(sourceId); + onSidebarOpen?.(rowWhere); + }, + [setRowId, setRowSource, sourceId, onSidebarOpen], + ); + + const onCloseSidebar = useCallback(() => { + setRowId(null); + setRowSource(null); + }, [setRowId, setRowSource]); + + return ( + + {sourceData && ( + + )} + { + if (!sourceData) { + return
Loading...
; + } + return ( + + ); + }} + onScroll={onScroll} + onError={onError} + onExpandedRowsChange={onExpandedRowsChange} + collapseAllRows={collapseAllRows} + /> +
+ ); +} + +enum InlineTab { + Overview = 'overview', + ColumnValues = 'columnValues', +} + +function RowOverviewPanelWrapper({ + source, + rowId, +}: { + source: TSource; + rowId: string; +}) { + // Use localStorage to persist the selected tab + const [activeTab, setActiveTab] = useLocalStorage( + 'hdx-expanded-row-default-tab', + InlineTab.ColumnValues, + ); + + return ( +
+
+ +
+
+ {activeTab === InlineTab.Overview && ( +
+ +
+ )} + {activeTab === InlineTab.ColumnValues && ( + + )} +
+
+ ); +} diff --git a/packages/app/src/components/ExpandableRowTable.tsx b/packages/app/src/components/ExpandableRowTable.tsx index 41b7b317ed..0f8a01d5f2 100644 --- a/packages/app/src/components/ExpandableRowTable.tsx +++ b/packages/app/src/components/ExpandableRowTable.tsx @@ -3,29 +3,17 @@ import cx from 'classnames'; import { useQueryState } from 'nuqs'; import { TSource } from '@hyperdx/common-utils/dist/types'; -import { useLocalStorage } from '@/utils'; - -import TabBar from '../TabBar'; - -import { RowDataPanel } from './DBRowDataPanel'; -import { RowOverviewPanel } from './DBRowOverviewPanel'; - import styles from '../../styles/LogTable.module.scss'; -enum InlineTab { - Overview = 'overview', - ColumnValues = 'columnValues', -} - // Hook that provides a function to open the sidebar with specific row details const useSidebarOpener = () => { const [, setRowId] = useQueryState('rowWhere'); const [, setRowSource] = useQueryState('rowSource'); return useCallback( - (rowWhere: string, sourceId: string) => { + (rowWhere: string, sourceId?: string) => { setRowId(rowWhere); - setRowSource(sourceId); + setRowSource(sourceId ?? null); }, [setRowId, setRowSource], ); @@ -34,27 +22,23 @@ const useSidebarOpener = () => { export const ExpandedLogRow = memo( ({ columnsLength, + children, virtualKey, source, rowId, measureElement, virtualIndex, }: { + children: React.ReactNode; columnsLength: number; virtualKey: string; - source: TSource | undefined; + source?: TSource; rowId: string; measureElement?: (element: HTMLElement | null) => void; virtualIndex?: number; }) => { const openSidebar = useSidebarOpener(); - // Use localStorage to persist the selected tab - const [activeTab, setActiveTab] = useLocalStorage( - 'hdx-expanded-row-default-tab', - InlineTab.ColumnValues, - ); - return (
- {source ? ( - <> -
-
- {openSidebar && ( - - )} - -
-
- {activeTab === InlineTab.Overview && ( -
- -
- )} - {activeTab === InlineTab.ColumnValues && ( - +
+
+ {openSidebar && ( +
- - ) : ( -
Loading...
- )} + onClick={() => openSidebar(rowId, source?.id)} + title="Open in sidebar" + aria-label="Open in sidebar" + style={{ + zIndex: 1, + fontSize: '12px', + lineHeight: 1, + }} + > + + + )} + {children} +
+
diff --git a/packages/app/src/components/PatternSidePanel.tsx b/packages/app/src/components/PatternSidePanel.tsx index bcdf607a87..4c59d106e9 100644 --- a/packages/app/src/components/PatternSidePanel.tsx +++ b/packages/app/src/components/PatternSidePanel.tsx @@ -128,7 +128,7 @@ export default function PatternSidePanel({ columnNameMap={columnNameMap} isLoading={false} fetchNextPage={() => {}} - onRowExpandClick={handleRowClick} + onRowDetailsClick={handleRowClick} wrapLines={false} highlightedLineId={''} hasNextPage={false} diff --git a/packages/app/src/components/PatternTable.tsx b/packages/app/src/components/PatternTable.tsx index a73b1c2b3d..621cd32a03 100644 --- a/packages/app/src/components/PatternTable.tsx +++ b/packages/app/src/components/PatternTable.tsx @@ -67,7 +67,7 @@ export default function PatternTable({ 'severityText', 'pattern', ]} - onRowExpandClick={row => setSelectedPattern(row as Pattern)} + onRowDetailsClick={row => setSelectedPattern(row as Pattern)} hasNextPage={false} fetchNextPage={() => {}} highlightedLineId={''} diff --git a/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx b/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx index 55e4820d86..39bcf7708e 100644 --- a/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx +++ b/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx @@ -1,18 +1,14 @@ -import { useCallback } from 'react'; -import { parseAsString, useQueryState } from 'nuqs'; import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse'; import type { Filter, TSource } from '@hyperdx/common-utils/dist/types'; import { Box, Code, Group, Text } from '@mantine/core'; import { ChartBox } from '@/components/ChartBox'; -import DBRowSidePanel from '@/components/DBRowSidePanel'; -import { DBSqlRowTable } from '@/components/DBRowTable'; import { useQueriedChartConfig } from '@/hooks/useChartConfig'; import { useJsonColumns } from '@/hooks/useMetadata'; import { getExpressions } from '@/serviceDashboard'; -import { useSource } from '@/source'; import { SQLPreview } from './ChartSQLPreview'; +import DBSqlRowTableWithSideBar from './DBSqlRowTableWithSidebar'; export default function SlowestEventsTile({ source, @@ -38,23 +34,6 @@ export default function SlowestEventsTile({ }); const expressions = getExpressions(source, jsonColumns); - const [rowId, setRowId] = useQueryState('rowId', parseAsString); - const [rowSource, setRowSource] = useQueryState('rowSource', parseAsString); - const { data: rowSidePanelSource } = useSource({ id: rowSource || '' }); - - const handleSidePanelClose = useCallback(() => { - setRowId(null); - setRowSource(null); - }, [setRowId, setRowSource]); - - const handleRowExpandClick = useCallback( - (rowWhere: string) => { - setRowId(rowWhere); - setRowSource(source.id); - }, - [source.id, setRowId, setRowSource], - ); - const { data, isLoading, isError, error } = useQueriedChartConfig( { ...source, @@ -130,7 +109,7 @@ export default function SlowestEventsTile({ ) : ( source && ( <> - {}} /> - {rowId && rowSidePanelSource && ( - - )} ) )} From e6091aeffcd19244c4cbf574043ea4e5d7b7c9c1 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Mon, 15 Sep 2025 16:04:32 -0600 Subject: [PATCH 2/7] make props optional, fix pattern table --- packages/app/src/components/DBRowTable.tsx | 17 +++++++++-------- .../app/src/components/PatternSidePanel.tsx | 6 ++---- packages/app/src/components/PatternTable.tsx | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index face3f1543..ac8502c14b 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -311,16 +311,16 @@ export const RawLogTable = memo( onSettingsClick?: () => void; onInstructionsClick?: () => void; rows: R[]; - isLoading: boolean; - fetchNextPage: (options?: FetchNextPageOptions | undefined) => any; + isLoading?: boolean; + fetchNextPage?: (options?: FetchNextPageOptions | undefined) => any; onRowDetailsClick: (row: R) => void; generateRowId: (row: R) => string; // onPropertySearchClick: ( // name: string, // value: string | number | boolean, // ) => void; - hasNextPage: boolean; - highlightedLineId: string | undefined; + hasNextPage?: boolean; + highlightedLineId?: string; onScroll?: (scrollTop: number) => void; isLive: boolean; onShowPatternsClick?: () => void; @@ -520,7 +520,7 @@ export const RawLogTable = memo( hasNextPage ) { // Cancel refetch is important to ensure we wait for the last fetch to finish - fetchNextPage({ cancelRefetch: false }); + fetchNextPage?.({ cancelRefetch: false }); } } }, @@ -619,13 +619,13 @@ export const RawLogTable = memo( const rowIdx = dedupedRows.findIndex( l => getRowId(l) === highlightedLineId, ); - if (rowIdx == -1) { + if (rowIdx == -1 && highlightedLineId) { if ( dedupedRows.length < MAX_SCROLL_FETCH_LINES && !isLoading && hasNextPage ) { - fetchNextPage({ cancelRefetch: false }); + fetchNextPage?.({ cancelRefetch: false }); } } else { setScrolledToHighlightedLine(true); @@ -840,7 +840,8 @@ export const RawLogTable = memo( role="button" // TODO: Restore highlight className={cx(styles.tableRow, { - [styles.tableRow__selected]: highlightedLineId === rowId, + [styles.tableRow__selected]: + highlightedLineId && highlightedLineId === rowId, })} data-index={virtualRow.index} ref={rowVirtualizer.measureElement} diff --git a/packages/app/src/components/PatternSidePanel.tsx b/packages/app/src/components/PatternSidePanel.tsx index 4c59d106e9..4e390420e1 100644 --- a/packages/app/src/components/PatternSidePanel.tsx +++ b/packages/app/src/components/PatternSidePanel.tsx @@ -126,12 +126,9 @@ export default function PatternSidePanel({ displayedColumns={displayedColumns} columnTypeMap={columnTypeMap} columnNameMap={columnNameMap} - isLoading={false} - fetchNextPage={() => {}} onRowDetailsClick={handleRowClick} wrapLines={false} - highlightedLineId={''} - hasNextPage={false} + showExpandButton={false} isLive={false} /> @@ -143,6 +140,7 @@ export default function PatternSidePanel({ rowId={selectedRowWhere} onClose={handleCloseRowSidePanel} isNestedPanel={true} + breadcrumbPath={[{ label: 'Pattern Overview' }]} /> )}
diff --git a/packages/app/src/components/PatternTable.tsx b/packages/app/src/components/PatternTable.tsx index 621cd32a03..6c82996d2b 100644 --- a/packages/app/src/components/PatternTable.tsx +++ b/packages/app/src/components/PatternTable.tsx @@ -80,6 +80,7 @@ export default function PatternTable({ severityText: 'level', }} config={patternQueryConfig} + showExpandButton={false} /> {selectedPattern && source && ( Date: Mon, 15 Sep 2025 16:10:27 -0600 Subject: [PATCH 3/7] add changeset --- .changeset/bright-suns-prove.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/bright-suns-prove.md diff --git a/.changeset/bright-suns-prove.md b/.changeset/bright-suns-prove.md new file mode 100644 index 0000000000..1a57a46b4c --- /dev/null +++ b/.changeset/bright-suns-prove.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +Reusable DBSqlRowTableWithSideBar Component From a17f571d1c2d55b775bee087c87e4b66f501310c Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Mon, 15 Sep 2025 16:22:23 -0600 Subject: [PATCH 4/7] improve code to catch early --- packages/app/src/components/DBRowTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 3fdda0179d..f4bb79779a 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -610,7 +610,7 @@ export const RawLogTable = memo( useEffect(() => { if ( scrolledToHighlightedLine || - highlightedLineId == null || + !highlightedLineId || rowVirtualizer == null ) { return; From 93c248c52d31a5a860280fc624a33e5af7c779f8 Mon Sep 17 00:00:00 2001 From: Brandon Pereira Date: Mon, 15 Sep 2025 16:26:17 -0600 Subject: [PATCH 5/7] bug fixes --- packages/app/src/PodDetailsSidePanel.tsx | 2 +- packages/app/src/components/DBEditTimeChartForm.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/app/src/PodDetailsSidePanel.tsx b/packages/app/src/PodDetailsSidePanel.tsx index 435ee2f03c..9608c21b8c 100644 --- a/packages/app/src/PodDetailsSidePanel.tsx +++ b/packages/app/src/PodDetailsSidePanel.tsx @@ -144,7 +144,7 @@ function PodLogs({ }) { const [resultType, setResultType] = React.useState<'all' | 'error'>('all'); - const _where = resultType === 'error' ? ' Severity:err' : ''; + const _where = where + (resultType === 'error' ? ' Severity:err' : ''); // Create a properly typed config object for DBSqlRowTable const tableConfig = React.useMemo(() => { diff --git a/packages/app/src/components/DBEditTimeChartForm.tsx b/packages/app/src/components/DBEditTimeChartForm.tsx index f26f1d551c..565a416c5b 100644 --- a/packages/app/src/components/DBEditTimeChartForm.tsx +++ b/packages/app/src/components/DBEditTimeChartForm.tsx @@ -979,7 +979,6 @@ export default function EditTimeChartForm({ className="flex-grow-1 d-flex flex-column" style={{ height: 400 }} > - aaa Date: Tue, 16 Sep 2025 15:26:40 -0600 Subject: [PATCH 6/7] fix bugs --- packages/app/src/DBDashboardPage.tsx | 35 +++++++++++-------- .../ServiceDashboardSlowestEventsTile.tsx | 2 ++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index fd6f70570b..ad656d8b6a 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -86,7 +86,7 @@ import { import { parseTimeQuery, useNewTimeQuery } from './timeQuery'; import { useConfirm } from './useConfirm'; import { getMetricTableName, hashCode, omit } from './utils'; -import { ZIndexContext } from './zIndex'; +import { useZIndex, ZIndexContext } from './zIndex'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; @@ -385,6 +385,8 @@ const EditTileModal = ({ isSaving?: boolean; onSave: (chart: Tile) => void; }) => { + const contextZIndex = useZIndex(); + const modalZIndex = contextZIndex + 10; return ( {chart != null && ( - {}} - dateRange={dateRange} - isSaving={isSaving} - onSave={config => { - onSave({ - ...chart, - config: config, - }); - }} - onClose={onClose} - /> + + {}} + dateRange={dateRange} + isSaving={isSaving} + onSave={config => { + onSave({ + ...chart, + config: config, + }); + }} + onClose={onClose} + /> + )} ); diff --git a/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx b/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx index 39bcf7708e..81fd5f11cd 100644 --- a/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx +++ b/packages/app/src/components/ServiceDashboardSlowestEventsTile.tsx @@ -110,6 +110,8 @@ export default function SlowestEventsTile({ source && ( <> Date: Tue, 16 Sep 2025 15:37:06 -0600 Subject: [PATCH 7/7] remove type inference (memo was breaking it anyways) --- packages/app/src/components/DBRowTable.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index f4bb79779a..23c771b2c6 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -273,7 +273,7 @@ const SqlModal = ({ }; export const RawLogTable = memo( - = Record>({ + ({ tableId, displayedColumns, fetchNextPage, @@ -310,11 +310,11 @@ export const RawLogTable = memo( displayedColumns: string[]; onSettingsClick?: () => void; onInstructionsClick?: () => void; - rows: R[]; + rows: Record[]; isLoading?: boolean; fetchNextPage?: (options?: FetchNextPageOptions | undefined) => any; - onRowDetailsClick: (row: R) => void; - generateRowId: (row: R) => string; + onRowDetailsClick: (row: Record) => void; + generateRowId: (row: Record) => string; // onPropertySearchClick: ( // name: string, // value: string | number | boolean, @@ -340,7 +340,7 @@ export const RawLogTable = memo( onExpandedRowsChange?: (hasExpandedRows: boolean) => void; collapseAllRows?: boolean; showExpandButton?: boolean; - renderRowDetails?: (row: R) => React.ReactNode; + renderRowDetails?: (row: Record) => React.ReactNode; }) => { const generateRowMatcher = generateRowId; @@ -365,7 +365,7 @@ export const RawLogTable = memo( const _onRowExpandClick = useCallback( ({ __hyperdx_id, ...row }: Record) => { - onRowDetailsClick?.(row as unknown as R); + onRowDetailsClick?.(row); }, [onRowDetailsClick], ); @@ -835,7 +835,6 @@ export const RawLogTable = memo( { - // onRowExpandClick(row.original.id, row.original.sort_key); _onRowExpandClick(row.original); }} role="button"