Skip to content

Commit 0183483

Browse files
authored
feat: Add source schema previews (#1182)
Closes HDX-2404 This PR adds a new info icon next to various Source pickers, which when clicked opens a modal that shows the schema(s) for the table(s) associated with the source. <details> <summary>On the search page</summary> <img width="483" height="263" alt="Screenshot 2025-09-18 at 1 57 14 PM" src="https://github.com/user-attachments/assets/84437e6d-f9da-4885-af87-b72767681b61" /> <img width="1367" height="923" alt="Screenshot 2025-09-18 at 4 15 12 PM" src="https://github.com/user-attachments/assets/1fe259f7-2cbf-480b-b3c3-5e94a298dd07" /> </details> <details> <summary>In the source form</summary> <img width="1122" height="657" alt="Screenshot 2025-09-18 at 1 57 57 PM" src="https://github.com/user-attachments/assets/0ffa3bfb-46df-45e6-8a64-188f52d7d1cb" /> <img width="1244" height="520" alt="Screenshot 2025-09-18 at 1 58 11 PM" src="https://github.com/user-attachments/assets/0c4fb035-afb0-4eda-8bdc-3d8b3ccd34c9" /> </details> <details> <summary>In the chart explorer</summary> <img width="559" height="221" alt="Screenshot 2025-09-18 at 1 57 33 PM" src="https://github.com/user-attachments/assets/8ea84e73-eb5d-445a-9faa-0180b5b9b8f9" /> </details> <details> <summary>Multiple schemas are shown when a metric source is chosen</summary> <img width="890" height="1044" alt="Screenshot 2025-09-18 at 4 14 37 PM" src="https://github.com/user-attachments/assets/f2463435-e9f5-4253-a3cb-2c76a74ea18e" /> </details>
1 parent 0d9f3fe commit 0183483

File tree

5 files changed

+219
-16
lines changed

5 files changed

+219
-16
lines changed

.changeset/strange-eagles-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/app": patch
3+
---
4+
5+
feat: Add source schema previews

packages/app/src/DBSearchPage.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import { QUERY_LOCAL_STORAGE, useLocalStorage, usePrevious } from '@/utils';
101101
import { SQLPreview } from './components/ChartSQLPreview';
102102
import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar';
103103
import PatternTable from './components/PatternTable';
104+
import SourceSchemaPreview from './components/SourceSchemaPreview';
104105
import { useTableMetadata } from './hooks/useMetadata';
105106
import { useSqlSuggestions } from './hooks/useSqlSuggestions';
106107
import api from './api';
@@ -1223,6 +1224,12 @@ function DBSearchPage() {
12231224
onCreate={openNewSourceModal}
12241225
allowedSourceKinds={[SourceKind.Log, SourceKind.Trace]}
12251226
/>
1227+
<span className="ms-1">
1228+
<SourceSchemaPreview
1229+
source={inputSourceObj}
1230+
iconStyles={{ size: 'xs', color: 'dark.2' }}
1231+
/>
1232+
</span>
12261233
<Menu withArrow position="bottom-start">
12271234
<Menu.Target>
12281235
<ActionIcon

packages/app/src/components/DBEditTimeChartForm.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
} from './InputControlled';
8383
import { MetricNameSelect } from './MetricNameSelect';
8484
import { NumberFormatInput } from './NumberFormat';
85+
import SourceSchemaPreview from './SourceSchemaPreview';
8586
import { SourceSelectControlled } from './SourceSelect';
8687

8788
const isQueryReady = (queriedConfig: ChartConfigWithDateRange | undefined) =>
@@ -671,6 +672,10 @@ export default function EditTimeChartForm({
671672
Data Source
672673
</Text>
673674
<SourceSelectControlled size="xs" control={control} name="source" />
675+
<SourceSchemaPreview
676+
source={tableSource}
677+
iconStyles={{ color: 'dark.2' }}
678+
/>
674679
</Flex>
675680

676681
{displayType !== DisplayType.Search && Array.isArray(select) ? (

packages/app/src/components/DBTableSelect.tsx

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { useController, UseControllerProps } from 'react-hook-form';
2-
import { Select } from '@mantine/core';
2+
import { Flex, Select } from '@mantine/core';
33

44
import { useTablesDirect } from '@/clickhouse';
55

6+
import SourceSchemaPreview from './SourceSchemaPreview';
7+
68
export default function DBTableSelect({
79
database,
810
setTable,
@@ -35,21 +37,35 @@ export default function DBTableSelect({
3537
}));
3638

3739
return (
38-
<Select
39-
searchable
40-
placeholder="Table"
41-
leftSection={<i className="bi bi-table"></i>}
42-
maxDropdownHeight={280}
43-
data={data}
44-
disabled={isTablesLoading}
45-
value={table}
46-
comboboxProps={{ withinPortal: false }}
47-
onChange={v => setTable(v ?? undefined)}
48-
onBlur={onBlur}
49-
name={name}
50-
ref={inputRef}
51-
size={size}
52-
/>
40+
<Flex align="center" gap={8}>
41+
<Select
42+
searchable
43+
placeholder="Table"
44+
leftSection={<i className="bi bi-table"></i>}
45+
maxDropdownHeight={280}
46+
data={data}
47+
disabled={isTablesLoading}
48+
value={table}
49+
comboboxProps={{ withinPortal: false }}
50+
onChange={v => setTable(v ?? undefined)}
51+
onBlur={onBlur}
52+
name={name}
53+
ref={inputRef}
54+
size={size}
55+
className="flex-grow-1"
56+
/>
57+
<SourceSchemaPreview
58+
source={
59+
connectionId && database && table
60+
? {
61+
connection: connectionId,
62+
from: { databaseName: database, tableName: table },
63+
}
64+
: undefined
65+
}
66+
iconStyles={{ color: 'gray.4' }}
67+
/>
68+
</Flex>
5369
);
5470
}
5571

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { useState } from 'react';
2+
import { MetricsDataType, TSource } from '@hyperdx/common-utils/dist/types';
3+
import { Modal, Paper, Tabs, Text, TextProps, Tooltip } from '@mantine/core';
4+
5+
import { useTableMetadata } from '@/hooks/useMetadata';
6+
7+
import { SQLPreview } from './ChartSQLPreview';
8+
9+
interface SourceSchemaInfoIconProps {
10+
onClick: () => void;
11+
isEnabled: boolean;
12+
tableCount: number;
13+
iconStyles?: Pick<TextProps, 'size' | 'color'>;
14+
}
15+
16+
const SourceSchemaInfoIcon = ({
17+
onClick,
18+
isEnabled,
19+
tableCount,
20+
iconStyles,
21+
}: SourceSchemaInfoIconProps) => {
22+
const tooltipText = isEnabled
23+
? tableCount > 1
24+
? `Show Table Schemas`
25+
: 'Show Table Schema'
26+
: 'Select a table to view its schema';
27+
28+
return (
29+
<Tooltip
30+
label={tooltipText}
31+
color="dark"
32+
c="white"
33+
position="right"
34+
onClick={() => isEnabled && onClick()}
35+
>
36+
<Text {...iconStyles}>
37+
<i
38+
className={`bi bi-info-circle ${isEnabled ? 'cursor-pointer' : ''}`}
39+
/>
40+
</Text>
41+
</Tooltip>
42+
);
43+
};
44+
45+
interface TableSchemaPreviewProps {
46+
databaseName: string;
47+
tableName: string;
48+
connectionId: string;
49+
}
50+
51+
const TableSchemaPreview = ({
52+
databaseName,
53+
tableName,
54+
connectionId,
55+
}: TableSchemaPreviewProps) => {
56+
const { data, isLoading } = useTableMetadata({
57+
databaseName,
58+
tableName,
59+
connectionId,
60+
});
61+
62+
return (
63+
<Paper flex="auto" shadow="none" radius="sm" style={{ overflow: 'hidden' }}>
64+
{isLoading ? (
65+
<div className="spin-animate d-inline-block">
66+
<i className="bi bi-arrow-repeat" />
67+
</div>
68+
) : (
69+
<SQLPreview
70+
data={data?.create_table_query ?? 'Schema is not available'}
71+
enableCopy={!!data?.create_table_query}
72+
/>
73+
)}
74+
</Paper>
75+
);
76+
};
77+
78+
export interface SourceSchemaPreviewProps {
79+
source?: Pick<TSource, 'connection' | 'from' | 'metricTables'> &
80+
Partial<Pick<TSource, 'kind' | 'name'>>;
81+
iconStyles?: Pick<TextProps, 'size' | 'color'>;
82+
}
83+
84+
const METRIC_TYPE_NAMES: Record<MetricsDataType, string> = {
85+
[MetricsDataType.Sum]: 'Sum',
86+
[MetricsDataType.Gauge]: 'Gauge',
87+
[MetricsDataType.Histogram]: 'Histogram',
88+
[MetricsDataType.Summary]: 'Summary',
89+
[MetricsDataType.ExponentialHistogram]: 'Exponential Histogram',
90+
};
91+
92+
const SourceSchemaPreview = ({
93+
source,
94+
iconStyles,
95+
}: SourceSchemaPreviewProps) => {
96+
const [isModalOpen, setIsModalOpen] = useState(false);
97+
98+
const isMetricSource = source?.kind === 'metric';
99+
const tables: (TableSchemaPreviewProps & { title: string })[] = [];
100+
if (source && isMetricSource) {
101+
tables.push(
102+
...Object.values(MetricsDataType)
103+
.map(metricType => ({
104+
metricType,
105+
tableName: source.metricTables?.[metricType],
106+
}))
107+
.filter(({ tableName }) => !!tableName)
108+
.map(({ metricType, tableName }) => ({
109+
databaseName: source.from.databaseName,
110+
tableName: tableName!,
111+
connectionId: source.connection,
112+
title: METRIC_TYPE_NAMES[metricType],
113+
})),
114+
);
115+
} else if (source && source.from.tableName) {
116+
tables.push({
117+
databaseName: source.from.databaseName,
118+
tableName: source.from.tableName,
119+
connectionId: source.connection,
120+
title: source.name ?? source.from.tableName,
121+
});
122+
}
123+
124+
const isEnabled = !!source && tables.length > 0;
125+
126+
return (
127+
<>
128+
<SourceSchemaInfoIcon
129+
isEnabled={isEnabled}
130+
onClick={() => setIsModalOpen(true)}
131+
iconStyles={iconStyles}
132+
tableCount={tables.length}
133+
/>
134+
{isEnabled && (
135+
<Modal
136+
opened={isModalOpen}
137+
onClose={() => setIsModalOpen(false)}
138+
size="auto"
139+
title={tables.length > 1 ? `Table Schemas` : `Table Schema`}
140+
>
141+
<Tabs
142+
defaultValue={`${tables[0]?.databaseName}.${tables[0]?.tableName}.${tables[0]?.title}`}
143+
>
144+
<Tabs.List>
145+
{tables.map(table => (
146+
<Tabs.Tab
147+
key={`${table.databaseName}.${table.tableName}.${table.title}`}
148+
value={`${table.databaseName}.${table.tableName}.${table.title}`}
149+
>
150+
{table.title}
151+
</Tabs.Tab>
152+
))}
153+
</Tabs.List>
154+
{tables.map(table => (
155+
<Tabs.Panel
156+
key={`${table.databaseName}.${table.tableName}.${table.title}`}
157+
value={`${table.databaseName}.${table.tableName}.${table.title}`}
158+
pt="sm"
159+
>
160+
<TableSchemaPreview {...table} />
161+
</Tabs.Panel>
162+
))}
163+
</Tabs>
164+
</Modal>
165+
)}
166+
</>
167+
);
168+
};
169+
170+
export default SourceSchemaPreview;

0 commit comments

Comments
 (0)