mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: remove useless session source fields (#1133)
This commit is contained in:
parent
81942a697e
commit
ecb20c8429
5 changed files with 127 additions and 83 deletions
6
.changeset/violet-kiwis-cover.md
Normal file
6
.changeset/violet-kiwis-cover.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: remove useless session source fields
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue