Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 171 additions & 62 deletions packages/app/src/components/Sources/SourceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useWatch,
} from 'react-hook-form';
import { z } from 'zod';
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
import {
MetricsDataType,
SourceKind,
Expand Down Expand Up @@ -41,10 +42,12 @@ import {
IconSettings,
IconTrash,
} from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';

import { SourceSelectControlled } from '@/components/SourceSelect';
import { IS_METRICS_ENABLED, IS_SESSIONS_ENABLED } from '@/config';
import { useConnections } from '@/connection';
import { useExplainQuery } from '@/hooks/useExplainQuery';
import {
inferTableSourceConfig,
isValidMetricTable,
Expand Down Expand Up @@ -168,6 +171,152 @@ function FormRow({
);
}

type HighlightedAttributeRowProps = Omit<TableModelProps, 'setValue'> & {
id: string;
index: number;
databaseName: string;
name:
| 'highlightedTraceAttributeExpressions'
| 'highlightedRowAttributeExpressions';
tableName: string;
connectionId: string;
removeHighlightedAttribute: (index: number) => void;
};

function HighlightedAttributeRow({
id,
index,
control,
databaseName,
name,
tableName,
connectionId,
removeHighlightedAttribute,
}: HighlightedAttributeRowProps) {
const expression = useWatch({
control,
name: `${name}.${index}.sqlExpression`,
});

const alias = useWatch({
control,
name: `${name}.${index}.alias`,
});

const {
data: explainData,
error: explainError,
isLoading: explainLoading,
refetch: explainExpression,
} = useExplainQuery(
{
from: { databaseName, tableName },
connection: connectionId,
select: [{ alias, valueExpression: expression }],
where: '',
},

{ enabled: false },
);

const runExpression = () => {
if (expression) {
explainExpression();
}
};

const isExpressionValid = !!explainData?.length;
const isExpressionInvalid = explainError instanceof ClickHouseQueryError;

return (
<React.Fragment key={id}>
<Grid.Col span={3} pe={0}>
<div
style={{ display: 'contents' }}
data-name={`${name}.${index}.sqlExpression`}
>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name={`${name}.${index}.sqlExpression`}
disableKeywordAutocomplete
placeholder="ResourceAttributes['http.host']"
/>
</div>
</Grid.Col>
<Grid.Col span={2} ps="xs">
<Flex align="center" gap="sm">
<Text c="gray">AS</Text>
<SQLInlineEditorControlled
control={control}
name={`${name}.${index}.alias`}
placeholder="Optional Alias"
disableKeywordAutocomplete
/>
<ActionIcon
size="xs"
variant="subtle"
color="gray"
onClick={() => removeHighlightedAttribute(index)}
>
<IconTrash size={16} />
</ActionIcon>
</Flex>
</Grid.Col>

<Grid.Col span={5} pe={0}>
<Flex align="center" gap="sm">
<Button
style={{ flexShrink: 0 }}
variant="outline"
size="xs"
loading={explainLoading}
disabled={!expression || explainLoading}
onClick={runExpression}
>
Validate expression
</Button>

{(isExpressionValid || isExpressionInvalid) && (
<Text c={isExpressionValid ? 'green' : 'red'}>
Expression is {isExpressionValid ? 'valid' : 'invalid'}.
</Text>
)}
</Flex>

{isExpressionInvalid && <Text mt="sm">{explainError?.message}</Text>}
</Grid.Col>

<Grid.Col span={3} pe={0}>
<InputControlled
control={control}
name={`${name}.${index}.luceneExpression`}
placeholder="ResourceAttributes.http.host (Optional) "
/>
</Grid.Col>
<Grid.Col span={1} pe={0}>
<Text me="sm" mt={6}>
<Tooltip
label={
'An optional, Lucene version of the above expression. If provided, it is used when searching for this attribute value.'
}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer" />
</Tooltip>
</Text>
</Grid.Col>
</React.Fragment>
);
}

function HighlightedAttributeExpressionsFormRow({
control,
name,
Expand Down Expand Up @@ -200,63 +349,20 @@ function HighlightedAttributeExpressionsFormRow({
return (
<FormRow label={label} helpText={helpText}>
<Grid columns={5}>
{highlightedAttributes.map((field, index) => (
<React.Fragment key={field.id}>
<Grid.Col span={3} pe={0}>
<SQLInlineEditorControlled
tableConnection={{
databaseName,
tableName,
connectionId,
}}
control={control}
name={`${name}.${index}.sqlExpression`}
disableKeywordAutocomplete
placeholder="ResourceAttributes['http.host']"
/>
</Grid.Col>
<Grid.Col span={2} ps="xs">
<Flex align="center" gap="sm">
<Text c="gray">AS</Text>
<SQLInlineEditorControlled
control={control}
name={`${name}.${index}.alias`}
placeholder="Optional Alias"
disableKeywordAutocomplete
/>
<ActionIcon
size="xs"
variant="subtle"
color="gray"
onClick={() => removeHighlightedAttribute(index)}
>
<IconTrash size={16} />
</ActionIcon>
</Flex>
</Grid.Col>
<Grid.Col span={3} pe={0}>
<InputControlled
control={control}
name={`${name}.${index}.luceneExpression`}
placeholder="ResourceAttributes.http.host (Optional) "
/>
</Grid.Col>
<Grid.Col span={1} pe={0}>
<Text me="sm" mt={6}>
<Tooltip
label={
'An optional, Lucene version of the above expression. If provided, it is used when searching for this attribute value.'
}
color="dark"
c="white"
multiline
maw={600}
>
<IconHelpCircle size={14} className="cursor-pointer" />
</Tooltip>
</Text>
</Grid.Col>
</React.Fragment>
{highlightedAttributes.map(({ id }, index) => (
<HighlightedAttributeRow
key={id}
{...{
id,
index,
name,
control,
databaseName,
tableName,
connectionId,
removeHighlightedAttribute,
}}
/>
))}
</Grid>
<Button
Expand All @@ -266,11 +372,14 @@ function HighlightedAttributeExpressionsFormRow({
className="align-self-start"
mt={highlightedAttributes.length ? 'sm' : 'md'}
onClick={() => {
appendHighlightedAttribute({
sqlExpression: '',
luceneExpression: '',
alias: '',
});
appendHighlightedAttribute(
{
sqlExpression: '',
luceneExpression: '',
alias: '',
},
{ shouldFocus: false },
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that the Lucene expression input was being focused for the newly appended field which was a bit confusing as that field is optional. This change stops any field being autofocused in the new row.

);
}}
>
<IconCirclePlus size={14} className="me-2" />
Expand Down
9 changes: 4 additions & 5 deletions packages/app/src/hooks/useExplainQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { renderChartConfig } from '@hyperdx/common-utils/dist/core/renderChartConfig';
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
import { ChartConfigWithOptDateRange } from '@hyperdx/common-utils/dist/types';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';

import { useClickhouseClient } from '@/clickhouse';
import { getMetadata } from '@/metadata';

export function useExplainQuery(
_config: ChartConfigWithDateRange,
_config: ChartConfigWithOptDateRange,
options?: Omit<UseQueryOptions<any>, 'queryKey' | 'queryFn'>,
) {
const config = {
..._config,
with: undefined,
};
const clickhouseClient = useClickhouseClient();
const { data, isLoading, error } = useQuery({

return useQuery({
queryKey: ['explain', config],
queryFn: async ({ signal }) => {
const query = await renderChartConfig(config, getMetadata());
Expand All @@ -31,6 +32,4 @@ export function useExplainQuery(
staleTime: 1000 * 60,
...options,
});

return { data, isLoading, error };
}
Loading