hyperdx/packages/app/src/components/DBSqlRowTableWithSidebar.tsx

203 lines
5.7 KiB
TypeScript
Raw Normal View History

import { useCallback, useState } from 'react';
import { useQueryState } from 'nuqs';
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
import {
feat: Add RawSqlChartConfig types for SQL-based Table (#1846) ## Summary This PR is the first step towards raw SQL-driven charts. - It introduces updated ChartConfig types, which are now unions of `BuilderChartConfig` (which is unchanged from the current `ChartConfig` types` and `RawSqlChartConfig` types which represent sql-driven charts. - It adds _very basic_ support for SQL-driven tables in the Chart Explorer and Dashboard pages. This is currently behind a feature toggle and enabled only in preview environments and for local development. The changes in most of the files in this PR are either type updates or the addition of type guards to handle the new ChartConfig union type. The DBEditTimeChartForm has been updated significantly to (a) add the Raw SQL option to the table chart editor and (b) handle conversion from internal form state (which can now include properties from either branch of the ChartConfig union) to valid SavedChartConfigs (which may only include properties from one branch). Significant changes are in: - packages/app/src/components/ChartEditor/types.ts - packages/app/src/components/ChartEditor/RawSqlChartEditor.tsx - packages/app/src/components/ChartEditor/utils.ts - packages/app/src/components/DBEditTimeChartForm.tsx - packages/app/src/components/DBTableChart.tsx - packages/app/src/components/SQLEditor.tsx - packages/app/src/hooks/useOffsetPaginatedQuery.tsx Future PRs will add templating to the Raw SQL driven charts for date range and granularity injection; support for other chart types driven by SQL; improved placeholder, validation, and error states; and improved support in the external API and import/export. ### Screenshots or video https://github.com/user-attachments/assets/008579cc-ef3c-496e-9899-88bbb21eaa5e ### How to test locally or on Vercel The SQL-driven table can be tested in the preview environment or locally. ### References - Linear Issue: HDX-3580 - Related PRs:
2026-03-05 20:30:58 +00:00
BuilderChartConfigWithDateRange,
TSource,
} from '@hyperdx/common-utils/dist/types';
2025-10-07 15:35:42 +00:00
import { SortingState } from '@tanstack/react-table';
import { RowWhereResult, WithClause } from '@/hooks/useRowWhere';
import { useSource } from '@/source';
import TabBar from '@/TabBar';
import { useLocalStorage } from '@/utils';
import { parseAsStringEncoded } from '@/utils/queryParsers';
import { useNestedPanelState } from './ContextSidePanel';
import { RowDataPanel } from './DBRowDataPanel';
import { RowOverviewPanel } from './DBRowOverviewPanel';
import DBRowSidePanel, {
RowSidePanelContext,
RowSidePanelContextProps,
} from './DBRowSidePanel';
import { BreadcrumbEntry } from './DBRowSidePanelHeader';
import { DBRowTableVariant, DBSqlRowTable } from './DBRowTable';
interface Props {
sourceId: string;
feat: Add RawSqlChartConfig types for SQL-based Table (#1846) ## Summary This PR is the first step towards raw SQL-driven charts. - It introduces updated ChartConfig types, which are now unions of `BuilderChartConfig` (which is unchanged from the current `ChartConfig` types` and `RawSqlChartConfig` types which represent sql-driven charts. - It adds _very basic_ support for SQL-driven tables in the Chart Explorer and Dashboard pages. This is currently behind a feature toggle and enabled only in preview environments and for local development. The changes in most of the files in this PR are either type updates or the addition of type guards to handle the new ChartConfig union type. The DBEditTimeChartForm has been updated significantly to (a) add the Raw SQL option to the table chart editor and (b) handle conversion from internal form state (which can now include properties from either branch of the ChartConfig union) to valid SavedChartConfigs (which may only include properties from one branch). Significant changes are in: - packages/app/src/components/ChartEditor/types.ts - packages/app/src/components/ChartEditor/RawSqlChartEditor.tsx - packages/app/src/components/ChartEditor/utils.ts - packages/app/src/components/DBEditTimeChartForm.tsx - packages/app/src/components/DBTableChart.tsx - packages/app/src/components/SQLEditor.tsx - packages/app/src/hooks/useOffsetPaginatedQuery.tsx Future PRs will add templating to the Raw SQL driven charts for date range and granularity injection; support for other chart types driven by SQL; improved placeholder, validation, and error states; and improved support in the external API and import/export. ### Screenshots or video https://github.com/user-attachments/assets/008579cc-ef3c-496e-9899-88bbb21eaa5e ### How to test locally or on Vercel The SQL-driven table can be tested in the preview environment or locally. ### References - Linear Issue: HDX-3580 - Related PRs:
2026-03-05 20:30:58 +00:00
config: BuilderChartConfigWithDateRange;
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[];
2025-10-07 15:35:42 +00:00
onSortingChange?: (v: SortingState | null) => void;
initialSortBy?: SortingState;
variant?: DBRowTableVariant;
}
export default function DBSqlRowTableWithSideBar({
sourceId,
config,
onError,
onScroll,
context,
onExpandedRowsChange,
denoiseResults,
collapseAllRows,
isLive,
enabled,
isNestedPanel,
breadcrumbPath,
onSidebarOpen,
2025-10-07 15:35:42 +00:00
onSortingChange,
initialSortBy,
variant,
}: Props) {
const { data: sourceData } = useSource({ id: sourceId });
const [rowId, setRowId] = useQueryState('rowWhere', parseAsStringEncoded);
const [rowSource, setRowSource] = useQueryState('rowSource');
const [aliasWith, setAliasWith] = useState<WithClause[]>([]);
const { setContextRowId, setContextRowSource } = useNestedPanelState();
const onOpenSidebar = useCallback(
(rowWhere: RowWhereResult) => {
setRowId(rowWhere.where);
setAliasWith(rowWhere.aliasWith);
setRowSource(sourceId);
onSidebarOpen?.(rowWhere.where);
},
[setRowId, setAliasWith, setRowSource, sourceId, onSidebarOpen],
);
const onCloseSidebar = useCallback(() => {
setRowId(null);
setRowSource(null);
// When closing the main drawer, clear the nested panel state
// this ensures that re-opening the main drawer will not open the nested panel
if (!isNestedPanel) {
setContextRowId(null);
setContextRowSource(null);
}
}, [
setRowId,
setRowSource,
isNestedPanel,
setContextRowId,
setContextRowSource,
]);
const renderRowDetails = useCallback(
(r: { id: string; aliasWith?: WithClause[]; [key: string]: unknown }) => {
if (!sourceData) {
return <div className="p-3 text-muted">Loading...</div>;
}
return (
<RowOverviewPanelWrapper
source={sourceData}
rowId={r.id}
aliasWith={r.aliasWith}
/>
);
},
[sourceData],
);
return (
<RowSidePanelContext.Provider value={context ?? {}}>
{sourceData && (rowSource === sourceId || !rowSource) && (
<DBRowSidePanel
source={sourceData}
rowId={rowId ?? undefined}
aliasWith={aliasWith}
isNestedPanel={isNestedPanel}
breadcrumbPath={breadcrumbPath}
onClose={onCloseSidebar}
/>
)}
<DBSqlRowTable
config={config}
sourceId={sourceId}
onRowDetailsClick={onOpenSidebar}
highlightedLineId={rowId ?? undefined}
enabled={enabled}
isLive={isLive ?? true}
queryKeyPrefix={'dbSqlRowTable'}
2025-10-07 15:35:42 +00:00
onSortingChange={onSortingChange}
denoiseResults={denoiseResults}
2025-10-07 15:35:42 +00:00
initialSortBy={initialSortBy}
renderRowDetails={renderRowDetails}
onScroll={onScroll}
onError={onError}
onExpandedRowsChange={onExpandedRowsChange}
collapseAllRows={collapseAllRows}
variant={variant}
/>
</RowSidePanelContext.Provider>
);
}
enum InlineTab {
Overview = 'overview',
ColumnValues = 'columnValues',
}
function RowOverviewPanelWrapper({
source,
rowId,
aliasWith,
}: {
source: TSource;
rowId: string;
aliasWith?: WithClause[];
}) {
// 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="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>
{activeTab === InlineTab.Overview && (
<div className="inline-overview-panel">
<RowOverviewPanel
source={source}
rowId={rowId}
aliasWith={aliasWith}
/>
</div>
)}
{activeTab === InlineTab.ColumnValues && (
<RowDataPanel source={source} rowId={rowId} aliasWith={aliasWith} />
)}
</div>
</div>
);
}