feat: remove useless session source fields (#1133)

This commit is contained in:
Drew Davis 2025-09-03 18:24:05 -04:00 committed by GitHub
parent 81942a697e
commit ecb20c8429
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 127 additions and 83 deletions

View file

@ -0,0 +1,6 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/app": patch
---
feat: remove useless session source fields

View file

@ -35,6 +35,7 @@ import { useConnections } from '@/connection';
import {
inferTableSourceConfig,
isValidMetricTable,
isValidSessionsTable,
useCreateSource,
useDeleteSource,
useSource,
@ -634,74 +635,53 @@ export function TraceTableModelForm({ control, watch }: TableModelProps) {
);
}
export function SessionTableModelForm({ control, watch }: TableModelProps) {
export function SessionTableModelForm({
control,
watch,
setValue,
}: TableModelProps) {
const databaseName = watch(`from.databaseName`, DEFAULT_DATABASE);
const tableName = watch(`from.tableName`);
const connectionId = watch(`connection`);
useEffect(() => {
const { unsubscribe } = watch(async (value, { name, type }) => {
try {
const tableName = value.from?.tableName;
if (tableName && name === 'from.tableName' && type === 'change') {
const isValid = await isValidSessionsTable({
databaseName,
tableName,
connectionId,
});
if (!isValid) {
notifications.show({
color: 'red',
message: `${tableName} is not a valid Sessions schema.`,
});
}
}
} catch (e) {
console.error(e);
notifications.show({
color: 'red',
message: e.message,
});
}
});
return () => unsubscribe();
}, [setValue, watch, databaseName, connectionId]);
return (
<>
<Stack gap="sm">
<FormRow
label={'Timestamp Column'}
helpText="DateTime column or expression that is part of your table's primary key."
>
<SQLInlineEditorControlled
tableConnections={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="timestampValueExpression"
disableKeywordAutocomplete
/>
</FormRow>
<FormRow label={'Log Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="eventAttributesExpression"
placeholder="LogAttributes"
/>
</FormRow>
<FormRow label={'Resource Attributes Expression'}>
<SQLInlineEditorControlled
tableConnections={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="resourceAttributesExpression"
placeholder="ResourceAttributes"
/>
</FormRow>
<FormRow
label={'Correlated Trace Source'}
helpText="HyperDX Source for traces associated with sessions. Required"
>
<SourceSelectControlled control={control} name="traceSourceId" />
</FormRow>
<FormRow
label={'Implicit Column Expression'}
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={{
databaseName,
tableName,
connectionId,
}}
control={control}
name="implicitColumnExpression"
placeholder="Body"
/>
</FormRow>
</Stack>
</>
);

View file

@ -17,7 +17,7 @@ import { usePrevious } from '@/utils';
import { getClickhouseClient, useClickhouseClient } from './clickhouse';
import { IS_LOCAL_MODE } from './config';
import { getLocalConnections } from './connection';
import { useSource } from './source';
import { SESSION_TABLE_EXPRESSIONS, useSource } from './source';
export type Session = {
errorCount: string;
@ -147,16 +147,18 @@ export function useSessions(
{
select: [
{
valueExpression: `DISTINCT ${sessionSource.resourceAttributesExpression}['rum.sessionId']`,
valueExpression: `DISTINCT ${SESSION_TABLE_EXPRESSIONS.resourceAttributesExpression}['rum.sessionId']`,
alias: 'sessionId',
},
],
from: sessionSource.from,
dateRange,
where: `${sessionSource.resourceAttributesExpression}['rum.sessionId'] IN (SELECT sessions.sessionId FROM ${SESSIONS_CTE_NAME})`,
where: `${SESSION_TABLE_EXPRESSIONS.resourceAttributesExpression}['rum.sessionId'] IN (SELECT sessions.sessionId FROM ${SESSIONS_CTE_NAME})`,
whereLanguage: 'sql',
timestampValueExpression: sessionSource.timestampValueExpression,
implicitColumnExpression: sessionSource.implicitColumnExpression,
timestampValueExpression:
SESSION_TABLE_EXPRESSIONS.timestampValueExpression,
implicitColumnExpression:
SESSION_TABLE_EXPRESSIONS.implicitColumnExpression,
connection: sessionSource.connection,
},
getMetadata(),
@ -335,19 +337,20 @@ export function useRRWebEventStream(
// FIXME: add mappings to session source
select: [
{
valueExpression: `${source.implicitColumnExpression}`,
valueExpression:
SESSION_TABLE_EXPRESSIONS.implicitColumnExpression,
alias: 'b',
},
{
valueExpression: `simpleJSONExtractInt(${source.implicitColumnExpression}, 'type')`,
valueExpression: `simpleJSONExtractInt(${SESSION_TABLE_EXPRESSIONS.implicitColumnExpression}, 'type')`,
alias: 't',
},
{
valueExpression: `${source.eventAttributesExpression}['rr-web.chunk']`,
valueExpression: `${SESSION_TABLE_EXPRESSIONS.eventAttributesExpression}['rr-web.chunk']`,
alias: 'ck',
},
{
valueExpression: `${source.eventAttributesExpression}['rr-web.total-chunks']`,
valueExpression: `${SESSION_TABLE_EXPRESSIONS.eventAttributesExpression}['rr-web.total-chunks']`,
alias: 'tcks',
},
],
@ -357,11 +360,13 @@ export function useRRWebEventStream(
],
from: source.from,
whereLanguage: 'lucene',
where: `ServiceName:"${serviceName}" AND ${source.resourceAttributesExpression}.rum.sessionId:"${sessionId}"`,
timestampValueExpression: source.timestampValueExpression,
implicitColumnExpression: source.implicitColumnExpression,
where: `ServiceName:"${serviceName}" AND ${SESSION_TABLE_EXPRESSIONS.resourceAttributesExpression}.rum.sessionId:"${sessionId}"`,
timestampValueExpression:
SESSION_TABLE_EXPRESSIONS.timestampValueExpression,
implicitColumnExpression:
SESSION_TABLE_EXPRESSIONS.implicitColumnExpression,
connection: source.connection,
orderBy: `${source.timestampValueExpression} ASC`,
orderBy: `${SESSION_TABLE_EXPRESSIONS.timestampValueExpression} ASC`,
limit: {
limit: Math.min(MAX_LIMIT, parseInt(queryLimit)),
offset: parseInt(offset),

View file

@ -9,7 +9,12 @@ import {
filterColumnMetaByType,
JSDataType,
} from '@hyperdx/common-utils/dist/clickhouse';
import { MetricsDataType, TSource } from '@hyperdx/common-utils/dist/types';
import {
MetricsDataType,
SourceKind,
TSource,
TSourceUnion,
} from '@hyperdx/common-utils/dist/types';
import {
hashCode,
splitAndTrimWithBracket,
@ -22,6 +27,14 @@ import { IS_LOCAL_MODE } from '@/config';
import { getMetadata } from '@/metadata';
import { parseJSON } from '@/utils';
// Columns for the sessions table as of OTEL Collector v0.129.1
export const SESSION_TABLE_EXPRESSIONS = {
resourceAttributesExpression: 'ResourceAttributes',
eventAttributesExpression: 'LogAttributes',
timestampValueExpression: 'TimestampTime',
implicitColumnExpression: 'Body',
} as const;
const LOCAL_STORE_SOUCES_KEY = 'hdx-local-source';
function setLocalSources(fn: (prev: TSource[]) => TSource[]) {
@ -74,6 +87,17 @@ export function getEventBody(eventModel: TSource) {
return multiExpr.length === 1 ? expression : multiExpr[0]; // TODO: check if we want to show multiple columns
}
function addDefaultsToSource(source: TSourceUnion): TSource {
return {
...source,
// Session sources have hard-coded timestampValueExpressions
timestampValueExpression:
source.kind === SourceKind.Session
? SESSION_TABLE_EXPRESSIONS.timestampValueExpression
: source.timestampValueExpression,
};
}
export function useSources() {
return useQuery({
queryKey: ['sources'],
@ -82,7 +106,8 @@ export function useSources() {
return getLocalSources();
}
return hdxServer('sources').json<TSource[]>();
const rawSources = await hdxServer('sources').json<TSourceUnion[]>();
return rawSources.map(addDefaultsToSource);
},
});
}
@ -92,7 +117,8 @@ export function useSource({ id }: { id?: string | null }) {
queryKey: ['sources'],
queryFn: async () => {
if (!IS_LOCAL_MODE) {
return hdxServer('sources').json<TSource[]>();
const rawSources = await hdxServer('sources').json<TSourceUnion[]>();
return rawSources.map(addDefaultsToSource);
} else {
return getLocalSources();
}
@ -408,3 +434,28 @@ export async function isValidMetricTable({
return hasAllColumns(columns, ReqMetricTableColumns[metricType]);
}
const ReqSessionsTableColumns = Object.values(SESSION_TABLE_EXPRESSIONS);
export async function isValidSessionsTable({
databaseName,
tableName,
connectionId,
}: {
databaseName: string;
tableName?: string;
connectionId: string;
}) {
if (!tableName) {
return false;
}
const metadata = getMetadata();
const columns = await metadata.getColumns({
databaseName,
tableName,
connectionId,
});
return hasAllColumns(columns, ReqSessionsTableColumns);
}

View file

@ -501,15 +501,19 @@ const SourceBaseSchema = z.object({
databaseName: z.string().min(1, 'Database is required'),
tableName: z.string().min(1, 'Table is required'),
}),
timestampValueExpression: z.string().min(1, 'Timestamp Column is required'),
});
const RequiredTimestampColumnSchema = z
.string()
.min(1, 'Timestamp Column is required');
// Log source form schema
const LogSourceAugmentation = {
kind: z.literal(SourceKind.Log),
defaultTableSelectExpression: z.string({
message: 'Default Table Select Expression is required',
}),
timestampValueExpression: RequiredTimestampColumnSchema,
// Optional fields for logs
serviceNameExpression: z.string().optional(),
@ -531,6 +535,7 @@ const LogSourceAugmentation = {
const TraceSourceAugmentation = {
kind: z.literal(SourceKind.Trace),
defaultTableSelectExpression: z.string().optional(),
timestampValueExpression: RequiredTimestampColumnSchema,
// Required fields for traces
durationExpression: z.string().min(1, 'Duration Expression is required'),
@ -561,18 +566,9 @@ const SessionSourceAugmentation = {
kind: z.literal(SourceKind.Session),
// Required fields for sessions
eventAttributesExpression: z
.string()
.min(1, 'Log Attributes Expression is required'),
resourceAttributesExpression: z
.string()
.min(1, 'Resource Attributes Expression is required'),
traceSourceId: z
.string({ message: 'Correlated Trace Source is required' })
.min(1, 'Correlated Trace Source is required'),
// Optional fields for sessions
implicitColumnExpression: z.string().optional(),
};
// Metric source form schema
@ -586,6 +582,7 @@ const MetricSourceAugmentation = {
// Metric tables - at least one should be provided
metricTables: MetricTableSchema,
timestampValueExpression: RequiredTimestampColumnSchema,
resourceAttributesExpression: z
.string()
.min(1, 'Resource Attributes is required'),
@ -659,4 +656,9 @@ type FlattenUnion<T> = {
? never
: K]?: T extends infer U ? (K extends keyof U ? U[K] : never) : never;
};
export type TSource = FlattenUnion<z.infer<typeof SourceSchema>>;
type TSourceWithoutDefaults = FlattenUnion<z.infer<typeof SourceSchema>>;
// Type representing a TSourceWithoutDefaults object which has been augmented with default values
export type TSource = TSourceWithoutDefaults & {
timestampValueExpression: string;
};