refactor: table connections standardized and single by default. Special case for dashboard (#1195)

Closes HDX-2469
This commit is contained in:
Aaron Knudtson 2025-10-02 17:33:53 -04:00 committed by GitHub
parent 1cda1485ed
commit 5210bb86a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 216 additions and 156 deletions

View file

@ -0,0 +1,6 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/app": patch
---
refactor: clean up table connections

View file

@ -1265,7 +1265,7 @@ function DBSearchPage() {
</Group>
<Box style={{ minWidth: 100, flexGrow: 1 }}>
<SQLInlineEditorControlled
tableConnections={tcFromSource(inputSourceObj)}
tableConnection={tcFromSource(inputSourceObj)}
control={control}
name="select"
defaultValue={inputSourceObj?.defaultTableSelectExpression}
@ -1279,7 +1279,7 @@ function DBSearchPage() {
</Box>
<Box style={{ maxWidth: 400, width: '20%' }}>
<SQLInlineEditorControlled
tableConnections={tcFromSource(inputSourceObj)}
tableConnection={tcFromSource(inputSourceObj)}
control={control}
name="orderBy"
defaultValue={defaultOrderBy}
@ -1401,7 +1401,7 @@ function DBSearchPage() {
sqlInput={
<Box style={{ width: '75%', flexGrow: 1 }}>
<SQLInlineEditorControlled
tableConnections={tcFromSource(inputSourceObj)}
tableConnection={tcFromSource(inputSourceObj)}
control={control}
name="where"
placeholder="SQL WHERE clause (ex. column = 'foo')"
@ -1421,7 +1421,7 @@ function DBSearchPage() {
}
luceneInput={
<SearchInputV2
tableConnections={tcFromSource(inputSourceObj)}
tableConnection={tcFromSource(inputSourceObj)}
control={control}
name="where"
onLanguageChange={lang =>

View file

@ -147,7 +147,7 @@ const AlertForm = ({
grouped by
</Text>
<SQLInlineEditorControlled
tableConnections={tcFromSource(source)}
tableConnection={tcFromSource(source)}
control={control}
name={`groupBy`}
placeholder="SQL Columns"

View file

@ -31,7 +31,7 @@ const DashboardFilterSelect = ({
const { data: keys, isLoading: isKeyValuesLoading } = useGetKeyValues(
{
chartConfigs: {
chartConfig: {
dateRange,
timestampValueExpression: timestampValueExpression!,
connection: connection!,

View file

@ -179,7 +179,7 @@ const DashboardFilterEditForm = ({
error={formState.errors.expression}
>
<SQLInlineEditorControlled
tableConnections={tableConnection}
tableConnection={tableConnection}
control={control}
name="expression"
placeholder="SQL column or expression"

View file

@ -281,7 +281,7 @@ export default function NamespaceDetailsSidePanel({
const { data: logServiceNames } = useGetKeyValues(
{
chartConfigs: {
chartConfig: {
from: logSource.from,
where: `${logSource?.resourceAttributesExpression}.k8s.namespace.name:"${namespaceName}"`,
whereLanguage: 'lucene',

View file

@ -297,7 +297,7 @@ export default function NodeDetailsSidePanel({
const { data: logServiceNames } = useGetKeyValues(
{
chartConfigs: {
chartConfig: {
from: logSource.from,
where: `${logSource?.resourceAttributesExpression}.k8s.node.name:"${nodeName}"`,
whereLanguage: 'lucene',

View file

@ -285,7 +285,7 @@ export default function PodDetailsSidePanel({
const { data: logServiceNames } = useGetKeyValues(
{
chartConfigs: {
chartConfig: {
from: logSource.from,
where: `${logSource?.resourceAttributesExpression}.k8s.pod.name:"${podName}"`,
whereLanguage: 'lucene',

View file

@ -1,7 +1,10 @@
import { useEffect, useRef, useState } from 'react';
import { useController, UseControllerProps } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { Field, TableConnection } from '@hyperdx/common-utils/dist/metadata';
import {
Field,
TableConnectionChoice,
} from '@hyperdx/common-utils/dist/metadata';
import { genEnglishExplanation } from '@hyperdx/common-utils/dist/queryParser';
import AutocompleteInput from '@/AutocompleteInput';
@ -25,6 +28,7 @@ export class LuceneLanguageFormatter implements ILanguageFormatter {
const luceneLanguageFormatter = new LuceneLanguageFormatter();
export default function SearchInputV2({
tableConnection,
tableConnections,
placeholder = 'Search your events for anything...',
size = 'sm',
@ -38,7 +42,6 @@ export default function SearchInputV2({
'data-testid': dataTestId,
...props
}: {
tableConnections?: TableConnection | TableConnection[];
placeholder?: string;
size?: 'xs' | 'sm' | 'lg';
zIndex?: number;
@ -49,7 +52,8 @@ export default function SearchInputV2({
additionalSuggestions?: string[];
queryHistoryType?: string;
'data-testid'?: string;
} & UseControllerProps<any>) {
} & UseControllerProps<any> &
TableConnectionChoice) {
const {
field: { onChange, value },
} = useController(props);
@ -59,9 +63,9 @@ export default function SearchInputV2({
const autoCompleteOptions = useAutoCompleteOptions(
luceneLanguageFormatter,
value != null ? `${value}` : '', // value can be null at times
value != null ? `${value}` : '',
{
tableConnections,
tableConnection: tableConnection ? tableConnection : tableConnections,
additionalSuggestions,
},
);

View file

@ -955,7 +955,7 @@ function ServicesDashboardPage() {
control={control}
sqlInput={
<SQLInlineEditorControlled
tableConnections={tcFromSource(source)}
tableConnection={tcFromSource(source)}
onSubmit={onSubmit}
control={control}
name="where"
@ -973,7 +973,7 @@ function ServicesDashboardPage() {
}
luceneInput={
<SearchInputV2
tableConnections={tcFromSource(source)}
tableConnection={tcFromSource(source)}
control={control}
name="where"
onLanguageChange={lang =>

View file

@ -488,7 +488,7 @@ export default function SessionSubpanel({
>
{whereLanguage === 'sql' ? (
<SQLInlineEditorControlled
tableConnections={tcFromSource(traceSource)}
tableConnection={tcFromSource(traceSource)}
control={control}
name="where"
placeholder="SQL WHERE clause (ex. column = 'foo')"
@ -498,7 +498,7 @@ export default function SessionSubpanel({
/>
) : (
<SearchInputV2
tableConnections={tcFromSource(traceSource)}
tableConnection={tcFromSource(traceSource)}
control={control}
name="where"
language="lucene"

View file

@ -449,7 +449,7 @@ export default function SessionsPage() {
sqlInput={
<Box style={{ width: '50%', flexGrow: 1 }}>
<SQLInlineEditorControlled
tableConnections={tcFromSource(traceTrace)}
tableConnection={tcFromSource(traceTrace)}
onSubmit={onSubmit}
control={control}
name="where"
@ -468,7 +468,7 @@ export default function SessionsPage() {
}
luceneInput={
<SearchInputV2
tableConnections={tcFromSource(traceTrace)}
tableConnection={tcFromSource(traceTrace)}
control={control}
name="where"
onLanguageChange={lang =>

View file

@ -260,7 +260,7 @@ export default function ContextSubpanel({
sqlInput={
originalLanguage === 'lucene' ? null : (
<SQLInlineEditorControlled
tableConnections={tcFromSource(source)}
tableConnection={tcFromSource(source)}
control={control}
name="where"
placeholder="SQL WHERE clause (ex. column = 'foo')"
@ -273,7 +273,7 @@ export default function ContextSubpanel({
luceneInput={
originalLanguage === 'sql' ? null : (
<SearchInputV2
tableConnections={tcFromSource(source)}
tableConnection={tcFromSource(source)}
control={control}
name="where"
language="lucene"

View file

@ -282,7 +282,7 @@ function ChartSeriesEditorComponent({
}}
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName: tableName ?? '',
connectionId: connectionId ?? '',
@ -299,7 +299,7 @@ function ChartSeriesEditorComponent({
<Text size="sm">Where</Text>
{aggConditionLanguage === 'sql' ? (
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName: tableName ?? '',
connectionId: connectionId ?? '',
@ -316,7 +316,7 @@ function ChartSeriesEditorComponent({
/>
) : (
<SearchInputV2
tableConnections={{
tableConnection={{
connectionId: connectionId ?? '',
databaseName: databaseName ?? '',
tableName: tableName ?? '',
@ -342,7 +342,7 @@ function ChartSeriesEditorComponent({
<div style={{ minWidth: 300 }}>
<SQLInlineEditorControlled
parentRef={parentRef}
tableConnections={{
tableConnection={{
databaseName,
tableName: tableName ?? '',
connectionId: connectionId ?? '',
@ -732,7 +732,7 @@ export default function EditTimeChartForm({
</Text>
<div style={{ flexGrow: 1 }}>
<SQLInlineEditorControlled
tableConnections={tcFromSource(tableSource)}
tableConnection={tcFromSource(tableSource)}
control={control}
name={`groupBy`}
placeholder="SQL Columns"
@ -808,7 +808,7 @@ export default function EditTimeChartForm({
) : (
<Flex gap="xs" direction="column">
<SQLInlineEditorControlled
tableConnections={tcFromSource(tableSource)}
tableConnection={tcFromSource(tableSource)}
control={control}
name="select"
placeholder={
@ -820,7 +820,7 @@ export default function EditTimeChartForm({
/>
{whereLanguage === 'sql' ? (
<SQLInlineEditorControlled
tableConnections={tcFromSource(tableSource)}
tableConnection={tcFromSource(tableSource)}
control={control}
name={`where`}
placeholder="SQL WHERE clause (ex. column = 'foo')"
@ -830,7 +830,7 @@ export default function EditTimeChartForm({
/>
) : (
<SearchInputV2
tableConnections={{
tableConnection={{
connectionId: tableSource?.connection ?? '',
databaseName: databaseName ?? '',
tableName: tableName ?? '',

View file

@ -10,6 +10,7 @@ import {
import cx from 'classnames';
import {
TableMetadata,
tcFromChartConfig,
tcFromSource,
} from '@hyperdx/common-utils/dist/metadata';
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
@ -554,28 +555,14 @@ const DBSearchPageFiltersComponent = ({
) => {
return _setFilterValue(property, value, action);
};
const {
toggleFilterPin,
toggleFieldPin,
isFilterPinned,
isFieldPinned,
getPinnedFields,
} = usePinnedFilters(sourceId ?? null);
const { toggleFilterPin, toggleFieldPin, isFilterPinned, isFieldPinned } =
usePinnedFilters(sourceId ?? null);
const { width, startResize } = useResizable(16, 'left');
const { data: countData } = useExplainQuery(chartConfig);
const numRows: number = countData?.[0]?.rows ?? 0;
const { data: jsonColumns } = useJsonColumns({
databaseName: chartConfig.from.databaseName,
tableName: chartConfig.from.tableName,
connectionId: chartConfig.connection,
});
const { data, isLoading, error } = useAllFields({
databaseName: chartConfig.from.databaseName,
tableName: chartConfig.from.tableName,
connectionId: chartConfig.connection,
});
const { data: jsonColumns } = useJsonColumns(tcFromChartConfig(chartConfig));
const { data, isLoading, error } = useAllFields(
tcFromChartConfig(chartConfig),
);
const { data: source } = useSource({ id: sourceId });
const { data: tableMetadata } = useTableMetadata(tcFromSource(source));
@ -647,7 +634,7 @@ const DBSearchPageFiltersComponent = ({
isLoading: isFacetsLoading,
isFetching: isFacetsFetching,
} = useGetKeyValues({
chartConfigs: { ...chartConfig, dateRange },
chartConfig: { ...chartConfig, dateRange },
limit: keyLimit,
keys: keysToFetch,
});

View file

@ -156,7 +156,7 @@ export default function DBTracePanel({
</Text>
<Flex>
<SQLInlineEditorControlled
tableConnections={tcFromSource(parentSourceData)}
tableConnection={tcFromSource(parentSourceData)}
name="traceIdExpression"
placeholder="Log Trace ID Column (ex. trace_id)"
control={traceIdControl}

View file

@ -37,7 +37,7 @@ const FilterSelect: React.FC<FilterSelectProps> = ({
dataTestId,
}) => {
const { data, isLoading } = useGetKeyValues({
chartConfigs: chartConfig,
chartConfig,
keys: [`${metricSource.resourceAttributesExpression}['${fieldName}']`],
});
@ -240,7 +240,7 @@ export const KubernetesFilters: React.FC<KubernetesFiltersProps> = ({
dataTestId="cluster-filter-select"
/>
<SearchInputV2
tableConnections={tcFromSource(metricSource)}
tableConnection={tcFromSource(metricSource)}
placeholder="Search query"
language="lucene"
name="searchQuery"

View file

@ -79,19 +79,19 @@ function useMetricNames(
}, [metricSource, dateRange]);
const { data: gaugeMetrics } = useGetKeyValues({
chartConfigs: gaugeConfig,
chartConfig: gaugeConfig,
keys: ['MetricName'],
limit: MAX_METRIC_NAME_OPTIONS,
disableRowLimit: true,
});
const { data: histogramMetrics } = useGetKeyValues({
chartConfigs: histogramConfig,
chartConfig: histogramConfig,
keys: ['MetricName'],
limit: MAX_METRIC_NAME_OPTIONS,
disableRowLimit: true,
});
const { data: sumMetrics } = useGetKeyValues({
chartConfigs: sumConfig,
chartConfig: sumConfig,
keys: ['MetricName'],
limit: MAX_METRIC_NAME_OPTIONS,
disableRowLimit: true,

View file

@ -9,7 +9,10 @@ import {
startCompletion,
} from '@codemirror/autocomplete';
import { sql, SQLDialect } from '@codemirror/lang-sql';
import { Field, TableConnection } from '@hyperdx/common-utils/dist/metadata';
import {
Field,
TableConnectionChoice,
} from '@hyperdx/common-utils/dist/metadata';
import { Paper, Text } from '@mantine/core';
import CodeMirror, {
Compartment,
@ -20,7 +23,7 @@ import CodeMirror, {
tooltips,
} from '@uiw/react-codemirror';
import { useAllFields } from '@/hooks/useMetadata';
import { useMultipleAllFields } from '@/hooks/useMetadata';
import { useQueryHistory } from '@/utils';
import InputLanguageSwitch from './InputLanguageSwitch';
@ -97,7 +100,6 @@ const AUTOCOMPLETE_LIST_FOR_SQL_FUNCTIONS = [
const AUTOCOMPLETE_LIST_STRING = ` ${AUTOCOMPLETE_LIST_FOR_SQL_FUNCTIONS.join(' ')}`;
type SQLInlineEditorProps = {
tableConnections?: TableConnection | TableConnection[];
autoCompleteFields?: Field[];
filterField?: (field: Field) => boolean;
value: string;
@ -143,6 +145,7 @@ const createStyleTheme = (allowMultiline: boolean = false) =>
});
export default function SQLInlineEditor({
tableConnection,
tableConnections,
filterField,
onChange,
@ -160,8 +163,11 @@ export default function SQLInlineEditor({
queryHistoryType,
parentRef,
allowMultiline = false,
}: SQLInlineEditorProps) {
const { data: fields } = useAllFields(tableConnections ?? []);
}: SQLInlineEditorProps & TableConnectionChoice) {
const _tableConnections = tableConnection
? [tableConnection]
: tableConnections;
const { data: fields } = useMultipleAllFields(_tableConnections ?? []);
const filteredFields = useMemo(() => {
return filterField ? fields?.filter(filterField) : fields;
}, [fields, filterField]);
@ -410,7 +416,9 @@ function SQLInlineEditorControlledComponent({
additionalSuggestions,
queryHistoryType,
...props
}: Omit<SQLInlineEditorProps, 'value' | 'onChange'> & UseControllerProps<any>) {
}: Omit<SQLInlineEditorProps, 'value' | 'onChange'> &
UseControllerProps<any> &
TableConnectionChoice) {
const { field, fieldState } = useController(props);
// Guard against wrongly typed values

View file

@ -165,7 +165,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
helpText="DateTime column or expression that is part of your table's primary key."
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -180,7 +180,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
helpText="Default columns selected in search results (this can be customized per search later)"
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -225,7 +225,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
<Divider />
<FormRow label={'Service Name Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -237,7 +237,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Log Level Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -249,7 +249,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Body Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -261,7 +261,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Log Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -273,7 +273,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Resource Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -288,7 +288,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
helpText="This DateTime column is used to display search results."
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -314,7 +314,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
<FormRow label={'Trace Id Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -326,7 +326,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Span Id Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -343,7 +343,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
helpText="Unique identifier for a given row, will be primary key if not specified. Used for showing full row details in search results."
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -355,7 +355,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
</FormRow> */}
{/* <FormRow label={'Table Filter Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -370,7 +370,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
helpText="Column used for full text search if no property is specified in a Lucene-based search. Typically the message body of a log."
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -397,7 +397,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
helpText="DateTime column or expression defines the start of the span"
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -413,7 +413,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
helpText="Default columns selected in search results (this can be customized per search later)"
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -426,7 +426,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
<Divider />
<FormRow label={'Duration Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -464,7 +464,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Trace Id Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -476,7 +476,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Span Id Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -488,7 +488,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Parent Span Id Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -500,7 +500,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Span Name Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -512,7 +512,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Span Kind Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -543,7 +543,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Status Code Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -555,7 +555,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Status Message Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -567,7 +567,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Service Name Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -579,7 +579,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Resource Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -591,7 +591,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
</FormRow>
<FormRow label={'Event Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -606,7 +606,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
helpText="Expression to extract span events. Used to capture events associated with spans. Expected to be Nested ( Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String)"
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,
@ -621,7 +621,7 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
helpText="Column used for full text search if no property is specified in a Lucene-based search. Typically the message body of a log."
>
<SQLInlineEditorControlled
tableConnections={{
tableConnection={{
databaseName,
tableName,
connectionId,

View file

@ -4,7 +4,11 @@ import { renderHook } from '@testing-library/react';
import { LuceneLanguageFormatter } from '../../SearchInputV2';
import { useAutoCompleteOptions } from '../useAutoCompleteOptions';
import { useAllFields, useGetKeyValues, useJsonColumns } from '../useMetadata';
import {
useJsonColumns,
useMultipleAllFields,
useMultipleGetKeyValues,
} from '../useMetadata';
if (!globalThis.structuredClone) {
globalThis.structuredClone = (obj: any) => {
@ -15,8 +19,8 @@ if (!globalThis.structuredClone) {
// Mock dependencies
jest.mock('../useMetadata', () => ({
...jest.requireActual('../useMetadata.tsx'),
useAllFields: jest.fn(),
useGetKeyValues: jest.fn(),
useMultipleAllFields: jest.fn(),
useMultipleGetKeyValues: jest.fn(),
useJsonColumns: jest.fn(),
}));
@ -40,13 +44,11 @@ const mockFields: Field[] = [
},
];
const mockTableConnections = [
{
databaseName: 'test_db',
tableName: 'traces',
connectionId: 'conn1',
},
];
const mockTableConnection = {
databaseName: 'test_db',
tableName: 'traces',
connectionId: 'conn1',
};
describe('useAutoCompleteOptions', () => {
beforeEach(() => {
@ -54,11 +56,11 @@ describe('useAutoCompleteOptions', () => {
jest.clearAllMocks();
// Setup default mock implementations
(useAllFields as jest.Mock).mockReturnValue({
(useMultipleAllFields as jest.Mock).mockReturnValue({
data: mockFields,
});
(useGetKeyValues as jest.Mock).mockReturnValue({
(useMultipleGetKeyValues as jest.Mock).mockReturnValue({
data: null,
});
@ -70,7 +72,7 @@ describe('useAutoCompleteOptions', () => {
it('should return field options with correct lucene formatting', () => {
const { result } = renderHook(() =>
useAutoCompleteOptions(luceneFormatter, 'ResourceAttributes', {
tableConnections: mockTableConnections,
tableConnection: mockTableConnection,
}),
);
@ -98,7 +100,7 @@ describe('useAutoCompleteOptions', () => {
},
];
(useGetKeyValues as jest.Mock).mockReturnValue({
(useMultipleGetKeyValues as jest.Mock).mockReturnValue({
data: mockKeyValues,
});
@ -107,7 +109,7 @@ describe('useAutoCompleteOptions', () => {
luceneFormatter,
'ResourceAttributes.service.name',
{
tableConnections: mockTableConnections,
tableConnection: mockTableConnection,
},
),
);
@ -150,13 +152,13 @@ describe('useAutoCompleteOptions', () => {
},
];
(useGetKeyValues as jest.Mock).mockReturnValue({
(useMultipleGetKeyValues as jest.Mock).mockReturnValue({
data: mockKeyValues,
});
const { result } = renderHook(() =>
useAutoCompleteOptions(luceneFormatter, 'ResourceAttributes', {
tableConnections: mockTableConnections,
tableConnection: mockTableConnection,
}),
);
@ -188,7 +190,7 @@ describe('useAutoCompleteOptions', () => {
it('should handle additional suggestions', () => {
const { result } = renderHook(() =>
useAutoCompleteOptions(luceneFormatter, 'ResourceAttributes', {
tableConnections: mockTableConnections,
tableConnection: mockTableConnection,
additionalSuggestions: ['custom.field'],
}),
);

View file

@ -6,7 +6,11 @@ import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, waitFor } from '@testing-library/react';
import { deduplicate2dArray, useGetKeyValues } from '../useMetadata';
import {
deduplicate2dArray,
useGetKeyValues,
useMultipleGetKeyValues,
} from '../useMetadata';
// Create a mock ChartConfig based on the Zod schema
const createMockChartConfig = (
@ -69,7 +73,7 @@ describe('useGetKeyValues', () => {
const { result } = renderHook(
() =>
useGetKeyValues({
chartConfigs: mockChartConfig,
chartConfig: mockChartConfig,
keys: mockKeys,
}),
{ wrapper },
@ -118,7 +122,7 @@ describe('useGetKeyValues', () => {
// Act
const { result } = renderHook(
() =>
useGetKeyValues({
useMultipleGetKeyValues({
chartConfigs: mockChartConfigs,
keys: mockKeys,
}),
@ -150,7 +154,7 @@ describe('useGetKeyValues', () => {
const { result } = renderHook(
() =>
useGetKeyValues({
chartConfigs: mockChartConfig,
chartConfig: mockChartConfig,
keys: [],
}),
{ wrapper },
@ -180,7 +184,7 @@ describe('useGetKeyValues', () => {
const { result } = renderHook(
() =>
useGetKeyValues({
chartConfigs: mockChartConfig,
chartConfig: mockChartConfig,
keys: mockKeys,
limit: 50,
disableRowLimit: true,
@ -206,7 +210,7 @@ describe('useGetKeyValues', () => {
const { result } = renderHook(
() =>
useGetKeyValues({
chartConfigs: mockChartConfig,
chartConfig: mockChartConfig,
keys: mockKeys,
}),
{ wrapper },

View file

@ -4,9 +4,9 @@ import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
import {
deduplicate2dArray,
useAllFields,
useGetKeyValues,
useJsonColumns,
useMultipleAllFields,
useMultipleGetKeyValues,
} from '@/hooks/useMetadata';
import { mergePath, toArray } from '@/utils';
@ -23,15 +23,21 @@ export function useAutoCompleteOptions(
formatter: ILanguageFormatter,
value: string,
{
tableConnections,
tableConnection,
additionalSuggestions,
}: {
tableConnections?: TableConnection | TableConnection[];
tableConnection?: TableConnection | TableConnection[];
additionalSuggestions?: string[];
},
) {
// Fetch and gather all field options
const { data: fields } = useAllFields(tableConnections ?? []);
const { data: fields } = useMultipleAllFields(
tableConnection
? Array.isArray(tableConnection)
? tableConnection
: [tableConnection]
: [],
);
const { fieldCompleteOptions, fieldCompleteMap } = useMemo(() => {
const _columns = (fields ?? []).filter(c => c.jsType !== null);
@ -72,11 +78,11 @@ export function useAutoCompleteOptions(
setSearchField(null);
}
}, [searchField, setSearchField, value, formatter]);
const tcForJson = Array.isArray(tableConnections)
? tableConnections.length > 0
? tableConnections[0]
const tcForJson = Array.isArray(tableConnection)
? tableConnection.length > 0
? tableConnection[0]
: undefined
: tableConnections;
: tableConnection;
const { data: jsonColumns } = useJsonColumns(
tcForJson ?? {
tableName: '',
@ -93,22 +99,22 @@ export function useAutoCompleteOptions(
);
// hooks to get key values
const chartConfigs: ChartConfigWithDateRange[] = toArray(
tableConnections,
).map(({ databaseName, tableName, connectionId }) => ({
connection: connectionId,
from: {
databaseName,
tableName,
},
timestampValueExpression: '',
select: '',
where: '',
// TODO: Pull in date for query as arg
// just assuming 1/2 day is okay to query over right now
dateRange: [new Date(NOW - (86400 * 1000) / 2), new Date(NOW)],
}));
const { data: keyVals } = useGetKeyValues({
const chartConfigs: ChartConfigWithDateRange[] = toArray(tableConnection).map(
({ databaseName, tableName, connectionId }) => ({
connection: connectionId,
from: {
databaseName,
tableName,
},
timestampValueExpression: '',
select: '',
where: '',
// TODO: Pull in date for query as arg
// just assuming 1/2 day is okay to query over right now
dateRange: [new Date(NOW - (86400 * 1000) / 2), new Date(NOW)],
}),
);
const { data: keyVals } = useMultipleGetKeyValues({
chartConfigs,
keys: searchKeys,
});

View file

@ -68,41 +68,39 @@ export function useColumns(
}
export function useJsonColumns(
{ databaseName, tableName, connectionId }: TableConnection,
tableConnection: TableConnection | undefined,
options?: Partial<UseQueryOptions<string[]>>,
) {
const metadata = useMetadataWithSettings();
return useQuery<string[]>({
queryKey: ['useMetadata.useJsonColumns', { databaseName, tableName }],
queryKey: ['useMetadata.useJsonColumns', tableConnection],
queryFn: async () => {
const columns = await metadata.getColumns({
databaseName,
tableName,
connectionId,
});
if (!tableConnection) return [];
const columns = await metadata.getColumns(tableConnection);
return (
filterColumnMetaByType(columns, [JSDataType.JSON])?.map(
column => column.name,
) ?? []
);
},
enabled: !!databaseName && !!tableName && !!connectionId,
enabled:
tableConnection &&
!!tableConnection.databaseName &&
!!tableConnection.tableName &&
!!tableConnection.connectionId,
...options,
});
}
export function useAllFields(
_tableConnections: TableConnection | TableConnection[],
export function useMultipleAllFields(
tableConnections: TableConnection[],
options?: Partial<UseQueryOptions<Field[]>>,
) {
const tableConnections = Array.isArray(_tableConnections)
? _tableConnections
: [_tableConnections];
const metadata = useMetadataWithSettings();
const { data: me, isFetched } = api.useMe();
return useQuery<Field[]>({
queryKey: [
'useMetadata.useAllFields',
'useMetadata.useMultipleAllFields',
...tableConnections.map(tc => ({ ...tc })),
],
queryFn: async () => {
@ -130,6 +128,16 @@ export function useAllFields(
});
}
export function useAllFields(
tableConnection: TableConnection | undefined,
options?: Partial<UseQueryOptions<Field[]>>,
) {
return useMultipleAllFields(
tableConnection ? [tableConnection] : [],
options,
);
}
export function useTableMetadata(
{
databaseName,
@ -158,7 +166,7 @@ export function useTableMetadata(
});
}
export function useGetKeyValues(
export function useMultipleGetKeyValues(
{
chartConfigs,
keys,
@ -202,6 +210,31 @@ export function useGetKeyValues(
});
}
export function useGetKeyValues(
{
chartConfig,
keys,
limit,
disableRowLimit,
}: {
chartConfig?: ChartConfigWithDateRange;
keys: string[];
limit?: number;
disableRowLimit?: boolean;
},
options?: Omit<UseQueryOptions<any, Error>, 'queryKey'>,
) {
return useMultipleGetKeyValues(
{
chartConfigs: chartConfig ? [chartConfig] : [],
keys,
limit,
disableRowLimit,
},
options,
);
}
export function deduplicateArray<T extends object>(array: T[]): T[] {
return deduplicate2dArray([array]);
}

View file

@ -677,6 +677,16 @@ export type TableConnection = {
metricName?: string;
};
export type TableConnectionChoice =
| {
tableConnection?: never;
tableConnections?: TableConnection[];
}
| {
tableConnection?: TableConnection;
tableConnections?: never;
};
export function tcFromChartConfig(config?: ChartConfig): TableConnection {
return {
databaseName: config?.from?.databaseName ?? '',