mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Fix autocomplete for (#713)
Added an override to provide fields as an input to SearchInputV2 (for lucene) and SqlInlineEditorControlled (for sql), instead of fetching in those respective components. This fixes autocomplete for fields on DBDashboardPage, which was not working previously. I have a fix for field values coming in a future PR.   Ref: HDX-1471
This commit is contained in:
parent
e002c2f9c6
commit
b99236d774
16 changed files with 421 additions and 242 deletions
5
.changeset/odd-balloons-explode.md
Normal file
5
.changeset/odd-balloons-explode.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: autocomplete options for dashboard page
|
||||
|
|
@ -17,6 +17,7 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { TableConnection } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { AlertState } from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
ChartConfigWithDateRange,
|
||||
|
|
@ -68,6 +69,7 @@ import DBRowSidePanel from './components/DBRowSidePanel';
|
|||
import OnboardingModal from './components/OnboardingModal';
|
||||
import { Tags } from './components/Tags';
|
||||
import { useDashboardRefresh } from './hooks/useDashboardRefresh';
|
||||
import { useAllFields } from './hooks/useMetadata';
|
||||
import { DEFAULT_CHART_CONFIG } from './ChartUtils';
|
||||
import { IS_LOCAL_MODE } from './config';
|
||||
import { useDashboard } from './dashboard';
|
||||
|
|
@ -506,6 +508,28 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
const { data: sources } = useSources();
|
||||
|
||||
const [highlightedTileId] = useQueryState('highlightedTileId');
|
||||
const tableConnections = useMemo(() => {
|
||||
if (!dashboard) return [];
|
||||
const tc: TableConnection[] = [];
|
||||
|
||||
for (const { config } of dashboard.tiles) {
|
||||
const source = sources?.find(v => v.id === config.source);
|
||||
if (!source) continue;
|
||||
// TODO: will need to update this when we allow for multiple metrics per chart
|
||||
const firstSelect = config.select[0];
|
||||
const metricType =
|
||||
typeof firstSelect !== 'string' ? firstSelect?.metricType : undefined;
|
||||
const tableName = getMetricTableName(source, metricType);
|
||||
if (!tableName) continue;
|
||||
tc.push({
|
||||
databaseName: source.from.databaseName,
|
||||
tableName: tableName,
|
||||
connectionId: source.connection,
|
||||
});
|
||||
}
|
||||
|
||||
return tc;
|
||||
}, [dashboard, sources]);
|
||||
|
||||
const [granularity, setGranularity] = useQueryState(
|
||||
'granularity',
|
||||
|
|
@ -686,13 +710,6 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
],
|
||||
);
|
||||
|
||||
const uniqueSources = useMemo(() => {
|
||||
return [...new Set(dashboard?.tiles.map(tile => tile.config.source))];
|
||||
}, [dashboard?.tiles]);
|
||||
const { data: defaultSource } = useSource({ id: uniqueSources[0] });
|
||||
const defaultDatabaseName = defaultSource?.from.databaseName;
|
||||
const defaultTableName = defaultSource?.from.tableName;
|
||||
|
||||
const deleteDashboard = useDeleteDashboard();
|
||||
|
||||
// Search tile
|
||||
|
|
@ -886,9 +903,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
render={({ field }) =>
|
||||
field.value === 'sql' ? (
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={defaultSource?.connection}
|
||||
database={defaultDatabaseName}
|
||||
table={defaultTableName}
|
||||
tableConnections={tableConnections}
|
||||
control={control}
|
||||
name="where"
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -900,9 +915,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
/>
|
||||
) : (
|
||||
<SearchInputV2
|
||||
connectionId={defaultSource?.connection}
|
||||
database={defaultDatabaseName}
|
||||
table={defaultTableName}
|
||||
tableConnections={tableConnections}
|
||||
control={control}
|
||||
name="where"
|
||||
onLanguageChange={lang => setValue('whereLanguage', lang)}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { useForm } from 'react-hook-form';
|
|||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
ChartConfigWithDateRange,
|
||||
DisplayType,
|
||||
|
|
@ -564,8 +565,6 @@ function DBSearchPage() {
|
|||
// const { data: inputSourceObj } = useSource({ id: inputSource });
|
||||
const { data: inputSourceObjs } = useSources();
|
||||
const inputSourceObj = inputSourceObjs?.find(s => s.id === inputSource);
|
||||
const databaseName = inputSourceObj?.from.databaseName;
|
||||
const tableName = inputSourceObj?.from.tableName;
|
||||
|
||||
// When source changes, make sure select and orderby fields are set to default
|
||||
const defaultOrderBy = useMemo(
|
||||
|
|
@ -992,9 +991,7 @@ function DBSearchPage() {
|
|||
</Group>
|
||||
<Box style={{ minWidth: 100, flexGrow: 1 }}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={inputSourceObj?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(inputSourceObj)}
|
||||
control={control}
|
||||
name="select"
|
||||
defaultValue={inputSourceObj?.defaultTableSelectExpression}
|
||||
|
|
@ -1008,9 +1005,7 @@ function DBSearchPage() {
|
|||
</Box>
|
||||
<Box style={{ maxWidth: 400, width: '20%' }}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={inputSourceObj?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(inputSourceObj)}
|
||||
control={control}
|
||||
name="orderBy"
|
||||
defaultValue={defaultOrderBy}
|
||||
|
|
@ -1127,9 +1122,7 @@ function DBSearchPage() {
|
|||
control={control}
|
||||
sqlInput={
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={inputSourceObj?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(inputSourceObj)}
|
||||
control={control}
|
||||
name="where"
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -1146,9 +1139,7 @@ function DBSearchPage() {
|
|||
}
|
||||
luceneInput={
|
||||
<SearchInputV2
|
||||
connectionId={inputSourceObj?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(inputSourceObj)}
|
||||
control={control}
|
||||
name="where"
|
||||
onLanguageChange={lang =>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useForm } from 'react-hook-form';
|
|||
import { NativeSelect, NumberInput } from 'react-hook-form-mantine';
|
||||
import { z } from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
type Alert,
|
||||
AlertIntervalSchema,
|
||||
|
|
@ -83,10 +84,6 @@ const AlertForm = ({
|
|||
}) => {
|
||||
const { data: source } = useSource({ id: sourceId });
|
||||
|
||||
const databaseName = source?.from.databaseName;
|
||||
const tableName = source?.from.tableName;
|
||||
const connectionId = source?.connection;
|
||||
|
||||
const { control, handleSubmit, watch } = useForm<Alert>({
|
||||
defaultValues: defaultValues || {
|
||||
interval: '5m',
|
||||
|
|
@ -148,10 +145,8 @@ const AlertForm = ({
|
|||
grouped by
|
||||
</Text>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(source)}
|
||||
control={control}
|
||||
connectionId={connectionId}
|
||||
name={`groupBy`}
|
||||
placeholder="SQL Columns"
|
||||
disableKeywordAutocomplete
|
||||
|
|
|
|||
|
|
@ -1,30 +1,27 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useController, UseControllerProps } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { TableConnection } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { genEnglishExplanation } from '@hyperdx/common-utils/dist/queryParser';
|
||||
|
||||
import AutocompleteInput from '@/AutocompleteInput';
|
||||
import { useAllFields } from '@/hooks/useMetadata';
|
||||
|
||||
export default function SearchInputV2({
|
||||
database,
|
||||
tableConnections,
|
||||
placeholder = 'Search your events for anything...',
|
||||
size = 'sm',
|
||||
table,
|
||||
zIndex,
|
||||
language,
|
||||
onLanguageChange,
|
||||
connectionId,
|
||||
enableHotkey,
|
||||
onSubmit,
|
||||
additionalSuggestions,
|
||||
...props
|
||||
}: {
|
||||
database?: string;
|
||||
tableConnections?: TableConnection | TableConnection[];
|
||||
placeholder?: string;
|
||||
size?: 'xs' | 'sm' | 'lg';
|
||||
table?: string;
|
||||
connectionId: string | undefined;
|
||||
zIndex?: number;
|
||||
onLanguageChange?: (language: 'sql' | 'lucene') => void;
|
||||
language?: 'sql' | 'lucene';
|
||||
|
|
@ -38,16 +35,11 @@ export default function SearchInputV2({
|
|||
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
|
||||
const { data: fields } = useAllFields(
|
||||
{
|
||||
databaseName: database ?? '',
|
||||
tableName: table ?? '',
|
||||
connectionId: connectionId ?? '',
|
||||
},
|
||||
{
|
||||
enabled: !!database && !!table && !!connectionId,
|
||||
},
|
||||
);
|
||||
const { data: fields } = useAllFields(tableConnections ?? [], {
|
||||
enabled:
|
||||
!!tableConnections &&
|
||||
(Array.isArray(tableConnections) ? tableConnections.length > 0 : true),
|
||||
});
|
||||
|
||||
const autoCompleteOptions = useMemo(() => {
|
||||
const _columns = (fields ?? []).filter(c => c.jsType !== null);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
useQueryStates,
|
||||
} from 'nuqs';
|
||||
import { UseControllerProps, useForm } from 'react-hook-form';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { DisplayType, Filter, TSource } from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
Box,
|
||||
|
|
@ -915,9 +916,7 @@ function ServicesDashboardPage() {
|
|||
control={control}
|
||||
sqlInput={
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={source?.connection}
|
||||
database={source?.from?.databaseName}
|
||||
table={source?.from?.tableName}
|
||||
tableConnections={tcFromSource(source)}
|
||||
onSubmit={onSubmit}
|
||||
control={control}
|
||||
name="where"
|
||||
|
|
@ -934,9 +933,7 @@ function ServicesDashboardPage() {
|
|||
}
|
||||
luceneInput={
|
||||
<SearchInputV2
|
||||
connectionId={source?.connection}
|
||||
database={source?.from?.databaseName}
|
||||
table={source?.from?.tableName}
|
||||
tableConnections={tcFromSource(source)}
|
||||
control={control}
|
||||
name="where"
|
||||
onLanguageChange={lang =>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import throttle from 'lodash/throttle';
|
|||
import { parseAsInteger, useQueryState } from 'nuqs';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
ChartConfigWithOptDateRange,
|
||||
DateRange,
|
||||
|
|
@ -488,9 +489,7 @@ export default function SessionSubpanel({
|
|||
>
|
||||
{whereLanguage === 'sql' ? (
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={traceSource?.connection}
|
||||
database={traceSource?.from?.databaseName}
|
||||
table={traceSource?.from?.tableName}
|
||||
tableConnections={tcFromSource(traceSource)}
|
||||
control={control}
|
||||
name="where"
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -500,9 +499,7 @@ export default function SessionSubpanel({
|
|||
/>
|
||||
) : (
|
||||
<SearchInputV2
|
||||
connectionId={traceSource?.connection}
|
||||
database={traceSource?.from?.databaseName}
|
||||
table={traceSource?.from?.tableName}
|
||||
tableConnections={tcFromSource(traceSource)}
|
||||
control={control}
|
||||
name="where"
|
||||
language="lucene"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
useQueryParams,
|
||||
withDefault,
|
||||
} from 'use-query-params';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
DateRange,
|
||||
SearchCondition,
|
||||
|
|
@ -436,9 +437,7 @@ export default function SessionsPage() {
|
|||
control={control}
|
||||
sqlInput={
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={traceTrace?.connection}
|
||||
database={traceTrace?.from?.databaseName}
|
||||
table={traceTrace?.from?.tableName}
|
||||
tableConnections={tcFromSource(traceTrace)}
|
||||
onSubmit={onSubmit}
|
||||
control={control}
|
||||
name="where"
|
||||
|
|
@ -455,9 +454,7 @@ export default function SessionsPage() {
|
|||
}
|
||||
luceneInput={
|
||||
<SearchInputV2
|
||||
connectionId={traceTrace?.connection}
|
||||
database={traceTrace?.from?.databaseName}
|
||||
table={traceTrace?.from?.tableName}
|
||||
tableConnections={tcFromSource(traceTrace)}
|
||||
control={control}
|
||||
name="where"
|
||||
onLanguageChange={lang =>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
|
|||
import { sq } from 'date-fns/locale';
|
||||
import ms from 'ms';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
ChartConfigWithDateRange,
|
||||
TSource,
|
||||
|
|
@ -169,9 +170,7 @@ export default function ContextSubpanel({
|
|||
sqlInput={
|
||||
originalLanguage === 'lucene' ? null : (
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={source.connection}
|
||||
database={source.from.databaseName}
|
||||
table={source.from.tableName}
|
||||
tableConnections={tcFromSource(source)}
|
||||
control={control}
|
||||
name="where"
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -184,9 +183,7 @@ export default function ContextSubpanel({
|
|||
luceneInput={
|
||||
originalLanguage === 'sql' ? null : (
|
||||
<SearchInputV2
|
||||
connectionId={source.connection}
|
||||
database={source.from.databaseName}
|
||||
table={source.from.tableName}
|
||||
tableConnections={tcFromSource(source)}
|
||||
control={control}
|
||||
name="where"
|
||||
language="lucene"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { NativeSelect, NumberInput } from 'react-hook-form-mantine';
|
||||
import z from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
AlertBaseSchema,
|
||||
ChartConfigWithDateRange,
|
||||
|
|
@ -200,11 +201,13 @@ function ChartSeriesEditor({
|
|||
{tableSource?.kind !== SourceKind.Metric && aggFn !== 'count' && (
|
||||
<div style={{ minWidth: 220 }}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName: tableName ?? '',
|
||||
connectionId: connectionId ?? '',
|
||||
}}
|
||||
control={control}
|
||||
name={`${namePrefix}valueExpression`}
|
||||
connectionId={connectionId}
|
||||
placeholder="SQL Column"
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
|
|
@ -213,9 +216,11 @@ function ChartSeriesEditor({
|
|||
<Text size="sm">Where</Text>
|
||||
{aggConditionLanguage === 'sql' ? (
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
connectionId={connectionId}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName: tableName ?? '',
|
||||
connectionId: connectionId ?? '',
|
||||
}}
|
||||
control={control}
|
||||
name={`${namePrefix}aggCondition`}
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -228,9 +233,11 @@ function ChartSeriesEditor({
|
|||
/>
|
||||
) : (
|
||||
<SearchInputV2
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
connectionId: connectionId ?? '',
|
||||
databaseName: databaseName ?? '',
|
||||
tableName: tableName ?? '',
|
||||
}}
|
||||
control={control}
|
||||
name={`${namePrefix}aggCondition`}
|
||||
onLanguageChange={lang =>
|
||||
|
|
@ -249,10 +256,12 @@ function ChartSeriesEditor({
|
|||
</Text>
|
||||
<div style={{ minWidth: 300 }}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName: tableName ?? '',
|
||||
connectionId: connectionId ?? '',
|
||||
}}
|
||||
control={control}
|
||||
connectionId={connectionId}
|
||||
name={`groupBy`}
|
||||
placeholder="SQL Columns"
|
||||
disableKeywordAutocomplete
|
||||
|
|
@ -586,9 +595,7 @@ export default function EditTimeChartForm({
|
|||
</Text>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
connectionId={tableSource?.connection}
|
||||
tableConnections={tcFromSource(tableSource)}
|
||||
control={control}
|
||||
name={`groupBy`}
|
||||
placeholder="SQL Columns"
|
||||
|
|
@ -648,9 +655,7 @@ export default function EditTimeChartForm({
|
|||
) : (
|
||||
<Flex gap="xs" direction="column">
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={tableSource?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={tcFromSource(tableSource)}
|
||||
control={control}
|
||||
name="select"
|
||||
placeholder={
|
||||
|
|
@ -662,9 +667,7 @@ export default function EditTimeChartForm({
|
|||
/>
|
||||
{whereLanguage === 'sql' ? (
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
connectionId={tableSource?.connection}
|
||||
tableConnections={tcFromSource(tableSource)}
|
||||
control={control}
|
||||
name={`where`}
|
||||
placeholder="SQL WHERE clause (ex. column = 'foo')"
|
||||
|
|
@ -674,9 +677,11 @@ export default function EditTimeChartForm({
|
|||
/>
|
||||
) : (
|
||||
<SearchInputV2
|
||||
connectionId={tableSource?.connection}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
connectionId: tableSource?.connection ?? '',
|
||||
databaseName: databaseName ?? '',
|
||||
tableName: tableName ?? '',
|
||||
}}
|
||||
control={control}
|
||||
name="where"
|
||||
onLanguageChange={lang => setValue('whereLanguage', lang)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { parseAsJson, useQueryState } from 'nuqs';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { SourceKind } from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
ActionIcon,
|
||||
|
|
@ -176,9 +177,7 @@ export default function DBTracePanel({
|
|||
</Text>
|
||||
<Flex>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={parentSourceData?.id}
|
||||
database={parentSourceData?.from.databaseName}
|
||||
table={parentSourceData?.from.tableName}
|
||||
tableConnections={tcFromSource(parentSourceData)}
|
||||
name="traceIdExpression"
|
||||
placeholder="Log Trace ID Column (ex. trace_id)"
|
||||
control={traceIdControl}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useController, UseControllerProps } from 'react-hook-form';
|
|||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { acceptCompletion, startCompletion } from '@codemirror/autocomplete';
|
||||
import { sql, SQLDialect } from '@codemirror/lang-sql';
|
||||
import { Field } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { Field, TableConnection } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { Paper, Text } from '@mantine/core';
|
||||
import CodeMirror, {
|
||||
Compartment,
|
||||
|
|
@ -89,8 +89,8 @@ const AUTOCOMPLETE_LIST_FOR_SQL_FUNCTIONS = [
|
|||
const AUTOCOMPLETE_LIST_STRING = ` ${AUTOCOMPLETE_LIST_FOR_SQL_FUNCTIONS.join(' ')}`;
|
||||
|
||||
type SQLInlineEditorProps = {
|
||||
database?: string | undefined;
|
||||
table?: string | undefined;
|
||||
tableConnections?: TableConnection | TableConnection[];
|
||||
autoCompleteFields?: Field[];
|
||||
filterField?: (field: Field) => boolean;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
|
|
@ -101,7 +101,6 @@ type SQLInlineEditorProps = {
|
|||
size?: string;
|
||||
label?: React.ReactNode;
|
||||
disableKeywordAutocomplete?: boolean;
|
||||
connectionId: string | undefined;
|
||||
enableHotkey?: boolean;
|
||||
additionalSuggestions?: string[];
|
||||
};
|
||||
|
|
@ -119,33 +118,25 @@ const styleTheme = EditorView.baseTheme({
|
|||
});
|
||||
|
||||
export default function SQLInlineEditor({
|
||||
database,
|
||||
tableConnections,
|
||||
filterField,
|
||||
onChange,
|
||||
placeholder,
|
||||
onLanguageChange,
|
||||
language,
|
||||
onSubmit,
|
||||
table,
|
||||
value,
|
||||
size,
|
||||
label,
|
||||
disableKeywordAutocomplete,
|
||||
connectionId,
|
||||
enableHotkey,
|
||||
additionalSuggestions = [],
|
||||
}: SQLInlineEditorProps) {
|
||||
const { data: fields } = useAllFields(
|
||||
{
|
||||
databaseName: database ?? '',
|
||||
tableName: table ?? '',
|
||||
connectionId: connectionId ?? '',
|
||||
},
|
||||
{
|
||||
enabled: !!database && !!table && !!connectionId,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: fields } = useAllFields(tableConnections ?? [], {
|
||||
enabled:
|
||||
!!tableConnections &&
|
||||
(Array.isArray(tableConnections) ? tableConnections.length > 0 : true),
|
||||
});
|
||||
const filteredFields = useMemo(() => {
|
||||
return filterField ? fields?.filter(filterField) : fields;
|
||||
}, [fields, filterField]);
|
||||
|
|
@ -171,7 +162,6 @@ export default function SQLInlineEditor({
|
|||
viewRef.dispatch({
|
||||
effects: compartmentRef.current.reconfigure(
|
||||
sql({
|
||||
defaultTable: table ?? '',
|
||||
dialect: SQLDialect.define({
|
||||
keywords:
|
||||
keywords.join(' ') +
|
||||
|
|
@ -181,7 +171,7 @@ export default function SQLInlineEditor({
|
|||
),
|
||||
});
|
||||
},
|
||||
[filteredFields, table, additionalSuggestions],
|
||||
[filteredFields, additionalSuggestions],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -292,11 +282,8 @@ export default function SQLInlineEditor({
|
|||
}
|
||||
|
||||
export function SQLInlineEditorControlled({
|
||||
database,
|
||||
table,
|
||||
placeholder,
|
||||
filterField,
|
||||
connectionId,
|
||||
additionalSuggestions,
|
||||
...props
|
||||
}: Omit<SQLInlineEditorProps, 'value' | 'onChange'> & UseControllerProps<any>) {
|
||||
|
|
@ -304,13 +291,10 @@ export function SQLInlineEditorControlled({
|
|||
|
||||
return (
|
||||
<SQLInlineEditor
|
||||
database={database}
|
||||
filterField={filterField}
|
||||
onChange={field.onChange}
|
||||
placeholder={placeholder}
|
||||
table={table}
|
||||
value={field.value || props.defaultValue}
|
||||
connectionId={connectionId}
|
||||
additionalSuggestions={additionalSuggestions}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -159,12 +159,14 @@ export function LogTableModelForm({
|
|||
helpText="DateTime column or expression that is part of your table's primary key."
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="timestampValueExpression"
|
||||
disableKeywordAutocomplete
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
|
|
@ -172,12 +174,14 @@ export function LogTableModelForm({
|
|||
helpText="Default columns selected in search results (this can be customized per search later)"
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="defaultTableSelectExpression"
|
||||
placeholder="Timestamp, Body"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<Box>
|
||||
|
|
@ -215,52 +219,62 @@ export function LogTableModelForm({
|
|||
<Divider />
|
||||
<FormRow label={'Service Name Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="serviceNameExpression"
|
||||
placeholder="ServiceName"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Log Level Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="severityTextExpression"
|
||||
placeholder="SeverityText"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Body Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="bodyExpression"
|
||||
placeholder="Body"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Log Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="eventAttributesExpression"
|
||||
placeholder="LogAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Resource Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="resourceAttributesExpression"
|
||||
placeholder="ResourceAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
|
|
@ -268,12 +282,14 @@ export function LogTableModelForm({
|
|||
helpText="This DateTime column is used to display search results."
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="displayedTimestampValueExpression"
|
||||
disableKeywordAutocomplete
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<Divider />
|
||||
|
|
@ -292,22 +308,26 @@ export function LogTableModelForm({
|
|||
|
||||
<FormRow label={'Trace Id Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="traceIdExpression"
|
||||
placeholder="TraceId"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Span Id Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="spanIdExpression"
|
||||
placeholder="SpanId"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
|
|
@ -317,22 +337,26 @@ export function LogTableModelForm({
|
|||
helpText="Unique identifier for a given row, will be primary key if not specified. Used for showing full row details in search results."
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="uniqueRowIdExpression"
|
||||
placeholder="Timestamp, ServiceName, Body"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow> */}
|
||||
{/* <FormRow label={'Table Filter Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="tableFilterExpression"
|
||||
placeholder="ServiceName = 'only_this_service'"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow> */}
|
||||
<FormRow
|
||||
|
|
@ -340,12 +364,14 @@ export function LogTableModelForm({
|
|||
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
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="implicitColumnExpression"
|
||||
placeholder="Body"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
</Stack>
|
||||
|
|
@ -389,9 +415,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Timestamp Column'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="timestampValueExpression"
|
||||
placeholder="Timestamp"
|
||||
|
|
@ -403,20 +431,24 @@ export function TraceTableModelForm({
|
|||
helpText="Default columns selected in search results (this can be customized per search later)"
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="defaultTableSelectExpression"
|
||||
placeholder="Timestamp, ServiceName, StatusCode, Duration, SpanName"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<Divider />
|
||||
<FormRow label={'Duration Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="durationExpression"
|
||||
placeholder="Duration Column"
|
||||
|
|
@ -450,9 +482,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Trace Id Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="traceIdExpression"
|
||||
placeholder="TraceId"
|
||||
|
|
@ -460,9 +494,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Span Id Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="spanIdExpression"
|
||||
placeholder="SpanId"
|
||||
|
|
@ -470,9 +506,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Parent Span Id Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="parentSpanIdExpression"
|
||||
placeholder="ParentSpanId"
|
||||
|
|
@ -480,9 +518,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Span Name Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="spanNameExpression"
|
||||
placeholder="SpanName"
|
||||
|
|
@ -490,9 +530,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Span Kind Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="spanKindExpression"
|
||||
placeholder="SpanKind"
|
||||
|
|
@ -513,9 +555,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Status Code Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="statusCodeExpression"
|
||||
placeholder="StatusCode"
|
||||
|
|
@ -523,9 +567,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Status Message Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="statusMessageExpression"
|
||||
placeholder="StatusMessage"
|
||||
|
|
@ -533,9 +579,11 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Service Name Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
connectionId={connectionId}
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="serviceNameExpression"
|
||||
placeholder="ServiceName"
|
||||
|
|
@ -543,22 +591,26 @@ export function TraceTableModelForm({
|
|||
</FormRow>
|
||||
<FormRow label={'Resource Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="resourceAttributesExpression"
|
||||
placeholder="ResourceAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Event Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="eventAttributesExpression"
|
||||
placeholder="SpanAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
|
|
@ -566,12 +618,14 @@ export function TraceTableModelForm({
|
|||
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
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="implicitColumnExpression"
|
||||
placeholder="SpanName"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
</Stack>
|
||||
|
|
@ -620,32 +674,38 @@ export function SessionTableModelForm({
|
|||
helpText="DateTime column or expression that is part of your table's primary key."
|
||||
>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="timestampValueExpression"
|
||||
disableKeywordAutocomplete
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Log Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="eventAttributesExpression"
|
||||
placeholder="LogAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow label={'Resource Attributes Expression'}>
|
||||
<SQLInlineEditorControlled
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="resourceAttributesExpression"
|
||||
placeholder="ResourceAttributes"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow
|
||||
|
|
@ -659,12 +719,14 @@ export function SessionTableModelForm({
|
|||
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
|
||||
database={databaseName}
|
||||
table={tableName}
|
||||
tableConnections={{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}}
|
||||
control={control}
|
||||
name="implicitColumnExpression"
|
||||
placeholder="Body"
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
</FormRow>
|
||||
</Stack>
|
||||
|
|
|
|||
94
packages/app/src/hooks/__tests__/useMetadata.test.tsx
Normal file
94
packages/app/src/hooks/__tests__/useMetadata.test.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { deduplicate2dArray } from '../useMetadata';
|
||||
|
||||
describe('deduplicate2dArray', () => {
|
||||
// Test basic deduplication
|
||||
it('should remove duplicate objects across 2D array', () => {
|
||||
const input = [
|
||||
[
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
],
|
||||
[
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 3, name: 'Charlie' },
|
||||
],
|
||||
];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
{ id: 3, name: 'Charlie' },
|
||||
]);
|
||||
});
|
||||
|
||||
// Test with empty arrays
|
||||
it('should handle empty 2D array', () => {
|
||||
const input: object[][] = [];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Test with nested empty arrays
|
||||
it('should handle 2D array with empty subarrays', () => {
|
||||
const input = [[], [], []];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Test with complex objects
|
||||
it('should deduplicate complex nested objects', () => {
|
||||
const input = [
|
||||
[
|
||||
{ user: { id: 1, details: { name: 'Alice' } } },
|
||||
{ user: { id: 2, details: { name: 'Bob' } } },
|
||||
],
|
||||
[
|
||||
{ user: { id: 1, details: { name: 'Alice' } } },
|
||||
{ user: { id: 3, details: { name: 'Charlie' } } },
|
||||
],
|
||||
];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result).toEqual([
|
||||
{ user: { id: 1, details: { name: 'Alice' } } },
|
||||
{ user: { id: 2, details: { name: 'Bob' } } },
|
||||
{ user: { id: 3, details: { name: 'Charlie' } } },
|
||||
]);
|
||||
});
|
||||
|
||||
// Test with different types of objects
|
||||
it('should work with different types of objects', () => {
|
||||
const input: {
|
||||
value: any;
|
||||
}[][] = [
|
||||
[{ value: 'string' }, { value: 42 }],
|
||||
[{ value: 'string' }, { value: true }],
|
||||
];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
// Test order preservation
|
||||
it('should preserve the order of first occurrence', () => {
|
||||
const input = [
|
||||
[{ id: 1 }, { id: 2 }],
|
||||
[{ id: 1 }, { id: 3 }],
|
||||
[{ id: 4 }, { id: 2 }],
|
||||
];
|
||||
|
||||
const result = deduplicate2dArray(input);
|
||||
|
||||
expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
import objectHash from 'object-hash';
|
||||
import { ColumnMeta } from '@hyperdx/common-utils/dist/clickhouse';
|
||||
import { Field, TableMetadata } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
Field,
|
||||
isSingleTableConnection,
|
||||
TableConnection,
|
||||
TableMetadata,
|
||||
} from '@hyperdx/common-utils/dist/metadata';
|
||||
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
keepPreviousData,
|
||||
|
|
@ -36,26 +42,27 @@ export function useColumns(
|
|||
}
|
||||
|
||||
export function useAllFields(
|
||||
{
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}: {
|
||||
databaseName: string;
|
||||
tableName: string;
|
||||
connectionId: string;
|
||||
},
|
||||
_tableConnections: TableConnection | TableConnection[],
|
||||
options?: Partial<UseQueryOptions<Field[]>>,
|
||||
) {
|
||||
const tableConnections = isSingleTableConnection(_tableConnections)
|
||||
? [_tableConnections]
|
||||
: _tableConnections;
|
||||
const metadata = getMetadata();
|
||||
return useQuery<Field[]>({
|
||||
queryKey: ['useMetadata.useAllFields', { databaseName, tableName }],
|
||||
queryKey: [
|
||||
'useMetadata.useAllFields',
|
||||
...tableConnections.map(tc => ({ ...tc })),
|
||||
],
|
||||
queryFn: async () => {
|
||||
return metadata.getAllFields({
|
||||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
});
|
||||
const fields2d = await Promise.all(
|
||||
tableConnections.map(tc => metadata.getAllFields(tc)),
|
||||
);
|
||||
|
||||
// skip deduplication if not needed
|
||||
if (fields2d.length === 1) return fields2d[0];
|
||||
|
||||
return deduplicate2dArray<Field>(fields2d);
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
|
@ -115,3 +122,18 @@ export function useGetKeyValues({
|
|||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
export function deduplicate2dArray<T extends object>(array2d: T[][]): T[] {
|
||||
// deduplicate common fields
|
||||
const array: T[] = [];
|
||||
const set = new Set<string>();
|
||||
for (const _array of array2d) {
|
||||
for (const elem of _array) {
|
||||
const key = objectHash.sha1(elem);
|
||||
if (set.has(key)) continue;
|
||||
set.add(key);
|
||||
array.push(elem);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
tableExpr,
|
||||
} from '@/clickhouse';
|
||||
import { renderChartConfig } from '@/renderChartConfig';
|
||||
import type { ChartConfigWithDateRange } from '@/types';
|
||||
import type { ChartConfig, ChartConfigWithDateRange, TSource } from '@/types';
|
||||
|
||||
const DEFAULT_SAMPLE_SIZE = 1e6;
|
||||
|
||||
|
|
@ -353,11 +353,7 @@ export class Metadata {
|
|||
databaseName,
|
||||
tableName,
|
||||
connectionId,
|
||||
}: {
|
||||
databaseName: string;
|
||||
tableName: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
}: TableConnection) {
|
||||
const fields: Field[] = [];
|
||||
const columns = await this.getColumns({
|
||||
databaseName,
|
||||
|
|
@ -467,6 +463,39 @@ export type Field = {
|
|||
jsType: JSDataType | null;
|
||||
};
|
||||
|
||||
export type TableConnection = {
|
||||
databaseName: string;
|
||||
tableName: string;
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export function isSingleTableConnection(
|
||||
obj: TableConnection | TableConnection[],
|
||||
): obj is TableConnection {
|
||||
return (
|
||||
!Array.isArray(obj) &&
|
||||
typeof obj?.databaseName === 'string' &&
|
||||
typeof obj?.tableName === 'string' &&
|
||||
typeof obj?.connectionId === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function tcFromChartConfig(config?: ChartConfig): TableConnection {
|
||||
return {
|
||||
databaseName: config?.from?.databaseName ?? '',
|
||||
tableName: config?.from?.tableName ?? '',
|
||||
connectionId: config?.connection ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
export function tcFromSource(source?: TSource): TableConnection {
|
||||
return {
|
||||
databaseName: source?.from?.databaseName ?? '',
|
||||
tableName: source?.from?.tableName ?? '',
|
||||
connectionId: source?.connection ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
const __LOCAL_CACHE__ = new MetadataCache();
|
||||
|
||||
// TODO: better to init the Metadata object on the client side
|
||||
|
|
|
|||
Loading…
Reference in a new issue