mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Reusable DBSqlRowTableWithSideBar Component (#1171)
Across the app, we are inconsistent with when we can open the sidebar and expand functionality. This is because the sidebar and logic was managed by the parent component. Additionally, the expand logic was set to assume a certain structure that some places in the application could not support (ex clickhouse dashboard doesn't have a 'source'). As a result, I have created the `DBSqlRowTableWithSideBar` component which will manage a lot of the common use cases for us. This PR introduces that new component, and updates all references (that could be easily upgraded) to use the new component when applciable. The result: a lot less duplicate code (see # of lines removed) and the ability to more easily maintain the components down the road. This PR also fixes several bugs I found as I tested these flows, especially around sidebars opening subpanels. Fixes: HDX-2341
This commit is contained in:
parent
970c0027b8
commit
7a0580590d
17 changed files with 369 additions and 334 deletions
5
.changeset/bright-suns-prove.md
Normal file
5
.changeset/bright-suns-prove.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
Reusable DBSqlRowTableWithSideBar Component
|
||||
|
|
@ -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
|
||||
</Text>
|
||||
<DBSqlRowTable
|
||||
highlightedLineId={undefined}
|
||||
onRowExpandClick={() => {}}
|
||||
showExpandButton={false}
|
||||
renderRowDetails={row => {
|
||||
return (
|
||||
<ReactCodeMirror
|
||||
extensions={[sql()]}
|
||||
editable={false}
|
||||
value={formatSql(row.query)}
|
||||
theme="dark"
|
||||
lang="sql"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
config={{
|
||||
select: `event_time, query_kind,
|
||||
read_rows,
|
||||
|
|
@ -735,7 +747,6 @@ function ClickhousePage() {
|
|||
],
|
||||
limit: { limit: 100 },
|
||||
}}
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
</ChartBox>
|
||||
</Grid.Col>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -89,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';
|
||||
|
|
@ -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) {
|
||||
|
|
@ -351,7 +337,7 @@ const Tile = forwardRef(
|
|||
<HDXMarkdownChart config={queriedConfig} />
|
||||
)}
|
||||
{queriedConfig?.displayType === DisplayType.Search && (
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
enabled
|
||||
sourceId={chart.config.source}
|
||||
config={{
|
||||
|
|
@ -372,9 +358,6 @@ const Tile = forwardRef(
|
|||
groupBy: undefined,
|
||||
granularity: undefined,
|
||||
}}
|
||||
onRowExpandClick={handleRowExpandClick}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
onScroll={() => {}}
|
||||
isLive={false}
|
||||
queryKeyPrefix={'search'}
|
||||
/>
|
||||
|
|
@ -402,6 +385,8 @@ const EditTileModal = ({
|
|||
isSaving?: boolean;
|
||||
onSave: (chart: Tile) => void;
|
||||
}) => {
|
||||
const contextZIndex = useZIndex();
|
||||
const modalZIndex = contextZIndex + 10;
|
||||
return (
|
||||
<Modal
|
||||
opened={chart != null}
|
||||
|
|
@ -410,22 +395,25 @@ const EditTileModal = ({
|
|||
centered
|
||||
size="90%"
|
||||
padding="xs"
|
||||
zIndex={modalZIndex}
|
||||
>
|
||||
{chart != null && (
|
||||
<EditTimeChartForm
|
||||
dashboardId={dashboardId}
|
||||
chartConfig={chart.config}
|
||||
setChartConfig={config => {}}
|
||||
dateRange={dateRange}
|
||||
isSaving={isSaving}
|
||||
onSave={config => {
|
||||
onSave({
|
||||
...chart,
|
||||
config: config,
|
||||
});
|
||||
}}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<ZIndexContext.Provider value={modalZIndex + 10}>
|
||||
<EditTimeChartForm
|
||||
dashboardId={dashboardId}
|
||||
chartConfig={chart.config}
|
||||
setChartConfig={config => {}}
|
||||
dateRange={dateRange}
|
||||
isSaving={isSaving}
|
||||
onSave={config => {
|
||||
onSave({
|
||||
...chart,
|
||||
config: config,
|
||||
});
|
||||
}}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</ZIndexContext.Provider>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
|
|
@ -1068,13 +1056,6 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
>
|
||||
+ Add New Tile
|
||||
</Button>
|
||||
{rowId && rowSidePanelSource && (
|
||||
<DBRowSidePanel
|
||||
source={rowSidePanelSource}
|
||||
rowId={rowId}
|
||||
onClose={handleSidePanelClose}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -696,7 +693,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');
|
||||
|
|
@ -880,13 +876,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<
|
||||
|
|
@ -1010,7 +1002,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);
|
||||
|
|
@ -1472,25 +1464,6 @@ function DBSearchPage() {
|
|||
</Button>
|
||||
</Flex>
|
||||
</form>
|
||||
<RowSidePanelContext.Provider
|
||||
value={{
|
||||
onPropertyAddClick: searchFilters.setFilterValue,
|
||||
displayedColumns,
|
||||
toggleColumn,
|
||||
generateSearchUrl,
|
||||
dbSqlRowTableConfig,
|
||||
isChildModalOpen: isDrawerChildModalOpen,
|
||||
setChildModalOpen: setDrawerChildModalOpen,
|
||||
}}
|
||||
>
|
||||
{searchedSource && (
|
||||
<DBRowSidePanel
|
||||
source={searchedSource}
|
||||
rowId={rowId ?? undefined}
|
||||
onClose={() => setRowId(null)}
|
||||
/>
|
||||
)}
|
||||
</RowSidePanelContext.Provider>
|
||||
{searchedConfig != null && searchedSource != null && (
|
||||
<SaveSearchModal
|
||||
opened={saveSearchModalState != null}
|
||||
|
|
@ -1837,32 +1810,31 @@ function DBSearchPage() {
|
|||
</div>
|
||||
)}
|
||||
{chartConfig &&
|
||||
searchedConfig.source &&
|
||||
dbSqlRowTableConfig &&
|
||||
analysisMode === 'results' && (
|
||||
<RowSidePanelContext.Provider
|
||||
value={{
|
||||
<DBSqlRowTableWithSideBar
|
||||
context={{
|
||||
onPropertyAddClick: searchFilters.setFilterValue,
|
||||
displayedColumns,
|
||||
toggleColumn,
|
||||
generateSearchUrl,
|
||||
dbSqlRowTableConfig,
|
||||
isChildModalOpen: isDrawerChildModalOpen,
|
||||
setChildModalOpen: setDrawerChildModalOpen,
|
||||
}}
|
||||
>
|
||||
<DBSqlRowTable
|
||||
config={dbSqlRowTableConfig}
|
||||
sourceId={searchedConfig.source ?? ''}
|
||||
onRowExpandClick={onRowExpandClick}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
enabled={isReady}
|
||||
isLive={isLive ?? true}
|
||||
queryKeyPrefix={QUERY_KEY_PREFIX}
|
||||
onScroll={onTableScroll}
|
||||
onError={handleTableError}
|
||||
denoiseResults={denoiseResults}
|
||||
onExpandedRowsChange={handleExpandedRowsChange}
|
||||
collapseAllRows={collapseAllRows}
|
||||
/>
|
||||
</RowSidePanelContext.Provider>
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Box data-testid="kubernetes-dashboard-page" p="sm">
|
||||
<OnboardingModal requireSource={false} />
|
||||
|
|
@ -869,13 +850,6 @@ function KubernetesDashboardPage() {
|
|||
logSource={logSource}
|
||||
/>
|
||||
)}
|
||||
{rowId && rowSidePanelSource && (
|
||||
<DBRowSidePanel
|
||||
source={rowSidePanelSource}
|
||||
rowId={rowId}
|
||||
onClose={handleSidePanelClose}
|
||||
/>
|
||||
)}
|
||||
<Group justify="space-between">
|
||||
<Group>
|
||||
<Text c="gray.4" size="xl">
|
||||
|
|
@ -1047,7 +1021,7 @@ function KubernetesDashboardPage() {
|
|||
</Card.Section>
|
||||
<Card.Section p="md" py="sm" h={CHART_HEIGHT}>
|
||||
{logSource && (
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={logSource.id}
|
||||
config={{
|
||||
...logSource,
|
||||
|
|
@ -1090,11 +1064,8 @@ function KubernetesDashboardPage() {
|
|||
limit: { limit: 200, offset: 0 },
|
||||
dateRange,
|
||||
}}
|
||||
onRowExpandClick={handleRowExpandClick}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
isLive={false}
|
||||
queryKeyPrefix="k8s-dashboard-events"
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
)}
|
||||
</Card.Section>
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
</Flex>
|
||||
</Card.Section>
|
||||
<Card.Section p="md" py="sm" h={CHART_HEIGHT}>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={logSource.id}
|
||||
isNestedPanel
|
||||
breadcrumbPath={[{ label: 'Namespace Details' }]}
|
||||
config={{
|
||||
...logSource,
|
||||
where: _where,
|
||||
|
|
@ -214,12 +217,8 @@ function NamespaceLogs({
|
|||
limit: { limit: 200, offset: 0 },
|
||||
dateRange,
|
||||
}}
|
||||
onRowExpandClick={() => {}}
|
||||
highlightedLineId={undefined}
|
||||
isLive={false}
|
||||
queryKeyPrefix="k8s-dashboard-namespace-logs"
|
||||
onScroll={() => {}}
|
||||
showExpandButton={false}
|
||||
/>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
</Flex>
|
||||
</Card.Section>
|
||||
<Card.Section p="md" py="sm" h={CHART_HEIGHT}>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={logSource.id}
|
||||
isNestedPanel
|
||||
breadcrumbPath={[{ label: 'Node Details' }]}
|
||||
config={{
|
||||
...logSource,
|
||||
where: _where,
|
||||
|
|
@ -233,12 +236,8 @@ function NodeLogs({
|
|||
limit: { limit: 200, offset: 0 },
|
||||
dateRange,
|
||||
}}
|
||||
onRowExpandClick={() => {}}
|
||||
highlightedLineId={undefined}
|
||||
isLive={false}
|
||||
queryKeyPrefix="k8s-dashboard-node-logs"
|
||||
showExpandButton={false}
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
@ -206,14 +206,13 @@ function PodLogs({
|
|||
</Flex>
|
||||
</Card.Section>
|
||||
<Card.Section p="md" py="sm" h={CHART_HEIGHT}>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={logSource.id}
|
||||
config={tableConfig}
|
||||
onRowExpandClick={onRowClick}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
isLive={false}
|
||||
isNestedPanel
|
||||
breadcrumbPath={[{ label: 'Pods' }]}
|
||||
queryKeyPrefix="k8s-dashboard-pod-logs"
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -319,10 +319,11 @@ export default function ContextSubpanel({
|
|||
<DBSqlRowTable
|
||||
sourceId={source.id}
|
||||
highlightedLineId={rowId}
|
||||
showExpandButton={false}
|
||||
isLive={false}
|
||||
config={config}
|
||||
queryKeyPrefix={QUERY_KEY_PREFIX}
|
||||
onRowExpandClick={handleRowExpandClick}
|
||||
onRowDetailsClick={handleRowExpandClick}
|
||||
onChildModalOpen={setChildModalOpen}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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,8 @@ export default function EditTimeChartForm({
|
|||
className="flex-grow-1 d-flex flex-column"
|
||||
style={{ height: 400 }}
|
||||
>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={sourceId}
|
||||
config={{
|
||||
...queriedConfig,
|
||||
orderBy: [
|
||||
|
|
@ -1002,13 +1003,9 @@ export default function EditTimeChartForm({
|
|||
groupBy: undefined,
|
||||
granularity: undefined,
|
||||
}}
|
||||
onRowExpandClick={() => {}}
|
||||
highlightedLineId={undefined}
|
||||
enabled
|
||||
isLive={false}
|
||||
showExpandButton={false}
|
||||
queryKeyPrefix={'search'}
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1029,13 +1026,12 @@ export default function EditTimeChartForm({
|
|||
className="flex-grow-1 d-flex flex-column"
|
||||
style={{ height: 400 }}
|
||||
>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
sourceId={sourceId}
|
||||
config={sampleEventsConfig}
|
||||
highlightedLineId={undefined}
|
||||
enabled
|
||||
isLive={false}
|
||||
queryKeyPrefix={'search'}
|
||||
showExpandButton={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<RowSidePanelContextProps>({});
|
||||
|
||||
enum Tab {
|
||||
Overview = 'overview',
|
||||
|
|
@ -148,7 +150,7 @@ const DBRowSidePanel = ({
|
|||
: Tab.Parsed;
|
||||
|
||||
const [queryTab, setQueryTab] = useQueryState(
|
||||
'tab',
|
||||
'sidePanelTab',
|
||||
parseAsStringEnum<Tab>(Object.values(Tab)).withDefault(defaultTab),
|
||||
);
|
||||
|
||||
|
|
@ -498,50 +500,52 @@ export default function DBRowSidePanelErrorBoundary({
|
|||
}, ['mouseup', 'touchend']);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
data-testid="row-side-panel"
|
||||
customIdSuffix={`log-side-panel-${rowId}`}
|
||||
duration={0}
|
||||
open={rowId != null}
|
||||
onClose={() => {
|
||||
if (!subDrawerOpen) {
|
||||
_onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
size={`${width}vw`}
|
||||
zIndex={drawerZIndex}
|
||||
enableOverlay={subDrawerOpen}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel} ref={drawerRef}>
|
||||
<Box className={styles.panelDragBar} onMouseDown={startResize} />
|
||||
<OptionalPortal withinPortal={!isNestedPanel}>
|
||||
<Drawer
|
||||
data-testid="row-side-panel"
|
||||
customIdSuffix={`log-side-panel-${rowId}`}
|
||||
duration={300}
|
||||
open={rowId != null}
|
||||
onClose={() => {
|
||||
if (!subDrawerOpen) {
|
||||
_onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
size={`${width}vw`}
|
||||
zIndex={drawerZIndex}
|
||||
enableOverlay={subDrawerOpen}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel} ref={drawerRef}>
|
||||
<Box className={styles.panelDragBar} onMouseDown={startResize} />
|
||||
|
||||
<ErrorBoundary
|
||||
fallbackRender={error => (
|
||||
<Stack>
|
||||
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent p-4">
|
||||
An error occurred while rendering this event.
|
||||
</div>
|
||||
<ErrorBoundary
|
||||
fallbackRender={error => (
|
||||
<Stack>
|
||||
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent p-4">
|
||||
An error occurred while rendering this event.
|
||||
</div>
|
||||
|
||||
<div className="px-2 py-1 m-2 fs-7 font-monospace bg-dark-grey p-4">
|
||||
{error?.error?.message}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
>
|
||||
<DBRowSidePanel
|
||||
source={source}
|
||||
rowId={rowId}
|
||||
onClose={_onClose}
|
||||
isNestedPanel={isNestedPanel}
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
setSubDrawerOpen={setSubDrawerOpen}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</ZIndexContext.Provider>
|
||||
</Drawer>
|
||||
<div className="px-2 py-1 m-2 fs-7 font-monospace bg-dark-grey p-4">
|
||||
{error?.error?.message}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
>
|
||||
<DBRowSidePanel
|
||||
source={source}
|
||||
rowId={rowId}
|
||||
onClose={_onClose}
|
||||
isNestedPanel={isNestedPanel}
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
setSubDrawerOpen={setSubDrawerOpen}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</ZIndexContext.Provider>
|
||||
</Drawer>
|
||||
</OptionalPortal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ export const RawLogTable = memo(
|
|||
generateRowId,
|
||||
onInstructionsClick,
|
||||
// onPropertySearchClick,
|
||||
onRowExpandClick,
|
||||
onRowDetailsClick,
|
||||
onScroll,
|
||||
onSettingsClick,
|
||||
onShowPatternsClick,
|
||||
|
|
@ -296,6 +296,7 @@ export const RawLogTable = memo(
|
|||
loadingDate,
|
||||
config,
|
||||
onChildModalOpen,
|
||||
renderRowDetails,
|
||||
source,
|
||||
onExpandedRowsChange,
|
||||
collapseAllRows,
|
||||
|
|
@ -306,16 +307,16 @@ export const RawLogTable = memo(
|
|||
onSettingsClick?: () => void;
|
||||
onInstructionsClick?: () => void;
|
||||
rows: Record<string, any>[];
|
||||
isLoading: boolean;
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => any;
|
||||
onRowExpandClick: (row: Record<string, any>) => void;
|
||||
isLoading?: boolean;
|
||||
fetchNextPage?: (options?: FetchNextPageOptions | undefined) => any;
|
||||
onRowDetailsClick: (row: Record<string, any>) => void;
|
||||
generateRowId: (row: Record<string, any>) => 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;
|
||||
|
|
@ -335,6 +336,7 @@ export const RawLogTable = memo(
|
|||
onExpandedRowsChange?: (hasExpandedRows: boolean) => void;
|
||||
collapseAllRows?: boolean;
|
||||
showExpandButton?: boolean;
|
||||
renderRowDetails?: (row: Record<string, any>) => React.ReactNode;
|
||||
}) => {
|
||||
const generateRowMatcher = generateRowId;
|
||||
|
||||
|
|
@ -359,9 +361,9 @@ export const RawLogTable = memo(
|
|||
|
||||
const _onRowExpandClick = useCallback(
|
||||
({ __hyperdx_id, ...row }: Record<string, any>) => {
|
||||
onRowExpandClick(row);
|
||||
onRowDetailsClick?.(row);
|
||||
},
|
||||
[onRowExpandClick],
|
||||
[onRowDetailsClick],
|
||||
);
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
|
@ -514,7 +516,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 });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -604,7 +606,7 @@ export const RawLogTable = memo(
|
|||
useEffect(() => {
|
||||
if (
|
||||
scrolledToHighlightedLine ||
|
||||
highlightedLineId == null ||
|
||||
!highlightedLineId ||
|
||||
rowVirtualizer == null
|
||||
) {
|
||||
return;
|
||||
|
|
@ -613,13 +615,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);
|
||||
|
|
@ -825,7 +827,8 @@ export const RawLogTable = memo(
|
|||
<tr
|
||||
data-testid={`table-row-${rowId}`}
|
||||
className={cx(styles.tableRow, {
|
||||
[styles.tableRow__selected]: highlightedLineId === rowId,
|
||||
[styles.tableRow__selected]:
|
||||
highlightedLineId && highlightedLineId === rowId,
|
||||
})}
|
||||
data-index={virtualRow.index}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
|
|
@ -909,7 +912,12 @@ export const RawLogTable = memo(
|
|||
rowId={rowId}
|
||||
measureElement={rowVirtualizer.measureElement}
|
||||
virtualIndex={virtualRow.index}
|
||||
/>
|
||||
>
|
||||
{renderRowDetails?.({
|
||||
id: rowId,
|
||||
...row.original,
|
||||
})}
|
||||
</ExpandedLogRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
@ -1124,7 +1132,7 @@ function DBSqlRowTableComponent({
|
|||
config,
|
||||
sourceId,
|
||||
onError,
|
||||
onRowExpandClick,
|
||||
onRowDetailsClick,
|
||||
highlightedLineId,
|
||||
enabled = true,
|
||||
isLive = false,
|
||||
|
|
@ -1135,14 +1143,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;
|
||||
|
|
@ -1213,11 +1223,11 @@ function DBSqlRowTableComponent({
|
|||
|
||||
const getRowWhere = useRowWhere({ meta: data?.meta, aliasMap });
|
||||
|
||||
const _onRowExpandClick = useCallback(
|
||||
const _onRowDetailsClick = useCallback(
|
||||
(row: Record<string, any>) => {
|
||||
return onRowExpandClick?.(getRowWhere(row));
|
||||
return onRowDetailsClick?.(getRowWhere(row));
|
||||
},
|
||||
[onRowExpandClick, getRowWhere],
|
||||
[onRowDetailsClick, getRowWhere],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -1331,11 +1341,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}
|
||||
|
|
|
|||
162
packages/app/src/components/DBSqlRowTableWithSidebar.tsx
Normal file
162
packages/app/src/components/DBSqlRowTableWithSidebar.tsx
Normal file
|
|
@ -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 (
|
||||
<RowSidePanelContext.Provider value={context ?? {}}>
|
||||
{sourceData && (
|
||||
<DBRowSidePanel
|
||||
source={sourceData}
|
||||
rowId={rowId ?? undefined}
|
||||
isNestedPanel={isNestedPanel}
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
onClose={onCloseSidebar}
|
||||
/>
|
||||
)}
|
||||
<DBSqlRowTable
|
||||
config={config}
|
||||
sourceId={sourceId}
|
||||
onRowDetailsClick={onOpenSidebar}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
enabled={enabled}
|
||||
isLive={isLive ?? true}
|
||||
queryKeyPrefix={'dbSqlRowTable'}
|
||||
denoiseResults={denoiseResults}
|
||||
renderRowDetails={r => {
|
||||
if (!sourceData) {
|
||||
return <div className="p-3 text-muted">Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<RowOverviewPanelWrapper
|
||||
source={sourceData}
|
||||
rowId={r.id as string}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
onScroll={onScroll}
|
||||
onError={onError}
|
||||
onExpandedRowsChange={onExpandedRowsChange}
|
||||
collapseAllRows={collapseAllRows}
|
||||
/>
|
||||
</RowSidePanelContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
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<InlineTab>(
|
||||
'hdx-expanded-row-default-tab',
|
||||
InlineTab.ColumnValues,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="position-relative">
|
||||
<div className="bg-body px-3 pt-2 position-relative">
|
||||
<TabBar
|
||||
className="fs-8"
|
||||
items={[
|
||||
{
|
||||
text: 'Overview',
|
||||
value: InlineTab.Overview,
|
||||
},
|
||||
{
|
||||
text: 'Column Values',
|
||||
value: InlineTab.ColumnValues,
|
||||
},
|
||||
]}
|
||||
activeItem={activeTab}
|
||||
onClick={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-body">
|
||||
{activeTab === InlineTab.Overview && (
|
||||
<div className="inline-overview-panel">
|
||||
<RowOverviewPanel source={source} rowId={rowId} />
|
||||
</div>
|
||||
)}
|
||||
{activeTab === InlineTab.ColumnValues && (
|
||||
<RowDataPanel source={source} rowId={rowId} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,29 +4,17 @@ import { useQueryState } from 'nuqs';
|
|||
import { TSource } from '@hyperdx/common-utils/dist/types';
|
||||
import { IconChevronRight } from '@tabler/icons-react';
|
||||
|
||||
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],
|
||||
);
|
||||
|
|
@ -35,27 +23,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<InlineTab>(
|
||||
'hdx-expanded-row-default-tab',
|
||||
InlineTab.ColumnValues,
|
||||
);
|
||||
|
||||
return (
|
||||
<tr
|
||||
data-testid={`expanded-row-${rowId}`}
|
||||
|
|
@ -66,60 +50,30 @@ export const ExpandedLogRow = memo(
|
|||
>
|
||||
<td colSpan={columnsLength} className="p-0 border-0">
|
||||
<div className={cx('mx-2 mb-2 rounded', styles.expandedRowContent)}>
|
||||
{source ? (
|
||||
<>
|
||||
<div className="position-relative">
|
||||
<div className="bg-body px-3 pt-2 position-relative">
|
||||
{openSidebar && (
|
||||
<button
|
||||
type="button"
|
||||
className={cx(
|
||||
'position-absolute top-0 end-0 mt-1 me-1 p-1 border-0 bg-transparent text-muted rounded',
|
||||
styles.expandButton,
|
||||
)}
|
||||
onClick={() => openSidebar(rowId, source.id)}
|
||||
title="Open in sidebar"
|
||||
aria-label="Open in sidebar"
|
||||
style={{
|
||||
zIndex: 1,
|
||||
fontSize: '12px',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-arrows-angle-expand" />
|
||||
</button>
|
||||
<div className="position-relative">
|
||||
<div className="bg-body px-3 pt-2 position-relative">
|
||||
{openSidebar && (
|
||||
<button
|
||||
type="button"
|
||||
className={cx(
|
||||
'position-absolute top-0 end-0 mt-1 me-1 p-1 border-0 bg-transparent text-muted rounded',
|
||||
styles.expandButton,
|
||||
)}
|
||||
<TabBar
|
||||
className="fs-8"
|
||||
items={[
|
||||
{
|
||||
text: 'Overview',
|
||||
value: InlineTab.Overview,
|
||||
},
|
||||
{
|
||||
text: 'Column Values',
|
||||
value: InlineTab.ColumnValues,
|
||||
},
|
||||
]}
|
||||
activeItem={activeTab}
|
||||
onClick={setActiveTab}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-body">
|
||||
{activeTab === InlineTab.Overview && (
|
||||
<div className="inline-overview-panel">
|
||||
<RowOverviewPanel source={source} rowId={rowId} />
|
||||
</div>
|
||||
)}
|
||||
{activeTab === InlineTab.ColumnValues && (
|
||||
<RowDataPanel source={source} rowId={rowId} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="p-3 text-muted">Loading...</div>
|
||||
)}
|
||||
onClick={() => openSidebar(rowId, source?.id)}
|
||||
title="Open in sidebar"
|
||||
aria-label="Open in sidebar"
|
||||
style={{
|
||||
zIndex: 1,
|
||||
fontSize: '12px',
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
<i className="bi bi-arrows-angle-expand" />
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -126,12 +126,9 @@ export default function PatternSidePanel({
|
|||
displayedColumns={displayedColumns}
|
||||
columnTypeMap={columnTypeMap}
|
||||
columnNameMap={columnNameMap}
|
||||
isLoading={false}
|
||||
fetchNextPage={() => {}}
|
||||
onRowExpandClick={handleRowClick}
|
||||
onRowDetailsClick={handleRowClick}
|
||||
wrapLines={false}
|
||||
highlightedLineId={''}
|
||||
hasNextPage={false}
|
||||
showExpandButton={false}
|
||||
isLive={false}
|
||||
/>
|
||||
</Card>
|
||||
|
|
@ -143,6 +140,7 @@ export default function PatternSidePanel({
|
|||
rowId={selectedRowWhere}
|
||||
onClose={handleCloseRowSidePanel}
|
||||
isNestedPanel={true}
|
||||
breadcrumbPath={[{ label: 'Pattern Overview' }]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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={''}
|
||||
|
|
@ -80,6 +80,7 @@ export default function PatternTable({
|
|||
severityText: 'level',
|
||||
}}
|
||||
config={patternQueryConfig}
|
||||
showExpandButton={false}
|
||||
/>
|
||||
{selectedPattern && source && (
|
||||
<PatternSidePanel
|
||||
|
|
|
|||
|
|
@ -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,9 @@ export default function SlowestEventsTile({
|
|||
) : (
|
||||
source && (
|
||||
<>
|
||||
<DBSqlRowTable
|
||||
<DBSqlRowTableWithSideBar
|
||||
isNestedPanel
|
||||
breadcrumbPath={[{ label: 'Endpoint' }]}
|
||||
sourceId={source.id}
|
||||
config={{
|
||||
...source,
|
||||
|
|
@ -170,19 +151,9 @@ export default function SlowestEventsTile({
|
|||
},
|
||||
],
|
||||
}}
|
||||
onRowExpandClick={handleRowExpandClick}
|
||||
highlightedLineId={rowId ?? undefined}
|
||||
isLive={false}
|
||||
queryKeyPrefix="service-dashboard-slowest-transactions"
|
||||
onScroll={() => {}}
|
||||
/>
|
||||
{rowId && rowSidePanelSource && (
|
||||
<DBRowSidePanel
|
||||
source={rowSidePanelSource}
|
||||
rowId={rowId}
|
||||
onClose={handleSidePanelClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue