diff --git a/.changeset/sour-gifts-thank.md b/.changeset/sour-gifts-thank.md new file mode 100644 index 000000000..6c452f96e --- /dev/null +++ b/.changeset/sour-gifts-thank.md @@ -0,0 +1,7 @@ +--- +"@hyperdx/common-utils": patch +"@hyperdx/api": patch +"@hyperdx/app": patch +--- + +fix: bugs with showing non otel spans (ex. clickhouse opentelemetry span logs) diff --git a/packages/api/src/models/source.ts b/packages/api/src/models/source.ts index eaf1ed625..b60d7f394 100644 --- a/packages/api/src/models/source.ts +++ b/packages/api/src/models/source.ts @@ -63,6 +63,7 @@ export const Source = mongoose.model( spanKindExpression: String, statusCodeExpression: String, statusMessageExpression: String, + spanEventsValueExpression: String, metricTables: { type: { diff --git a/packages/app/src/components/DBRowDataPanel.tsx b/packages/app/src/components/DBRowDataPanel.tsx index 45a6661bd..dcdd98c25 100644 --- a/packages/app/src/components/DBRowDataPanel.tsx +++ b/packages/app/src/components/DBRowDataPanel.tsx @@ -82,10 +82,10 @@ export function useRowData({ }, ] : []), - ...(source.kind === SourceKind.Trace + ...(source.kind === SourceKind.Trace && source.spanEventsValueExpression ? [ { - valueExpression: `Events.Attributes[indexOf(Events.Name, 'exception')]`, + valueExpression: `${source.spanEventsValueExpression}.Attributes[indexOf(${source.spanEventsValueExpression}.Name, 'exception')]`, alias: '__hdx_events_exception_attributes', }, ] diff --git a/packages/app/src/components/DBRowSidePanel.tsx b/packages/app/src/components/DBRowSidePanel.tsx index bcd3887a3..476e2cb04 100644 --- a/packages/app/src/components/DBRowSidePanel.tsx +++ b/packages/app/src/components/DBRowSidePanel.tsx @@ -94,9 +94,21 @@ export default function DBRowSidePanel({ Infrastructure = 'infrastructure', } + const hasOverviewPanel = useMemo(() => { + if ( + source.resourceAttributesExpression || + source.eventAttributesExpression + ) { + return true; + } + return false; + }, [source.eventAttributesExpression, source.resourceAttributesExpression]); + + const defaultTab = hasOverviewPanel ? Tab.Overview : Tab.Parsed; + const [queryTab, setQueryTab] = useQueryState( 'tab', - parseAsStringEnum(Object.values(Tab)).withDefault(Tab.Overview), + parseAsStringEnum(Object.values(Tab)).withDefault(defaultTab), ); const initialWidth = 80; @@ -114,7 +126,7 @@ export default function DBRowSidePanel({ // Keep track of sub-drawers so we can disable closing this root drawer const [subDrawerOpen, setSubDrawerOpen] = useState(false); - const [stateTab, setStateTab] = useState(Tab.Overview); + const [stateTab, setStateTab] = useState(defaultTab); // Nested panels can't share the query param or else they'll conflict, so we'll use local state for nested panels // We'll need to handle this properly eventually... const tab = isNestedPanel ? stateTab : queryTab; @@ -211,15 +223,20 @@ export default function DBRowSidePanel({ }); const hasK8sContext = useMemo(() => { - if (!source?.resourceAttributesExpression || !normalizedRow) { + try { + if (!source?.resourceAttributesExpression || !normalizedRow) { + return false; + } + return ( + normalizedRow[source.resourceAttributesExpression]?.['k8s.pod.uid'] != + null || + normalizedRow[source.resourceAttributesExpression]?.['k8s.node.name'] != + null + ); + } catch (e) { + console.error(e); return false; } - return ( - normalizedRow[source.resourceAttributesExpression]['k8s.pod.uid'] != - null || - normalizedRow[source.resourceAttributesExpression]['k8s.node.name'] != - null - ); }, [source, normalizedRow]); return ( @@ -264,10 +281,14 @@ export default function DBRowSidePanel({ - + @@ -413,7 +413,10 @@ export function TraceTableModelForm({ rules={{ required: 'Table is required' }} /> - + + + + col.name === 'Events'); + const timestampColumns = filterColumnMetaByType(columns, [JSDataType.Date]); const primaryKeyTimestampColumn = timestampColumns?.find(c => keys.find( @@ -299,17 +302,18 @@ export async function inferTableSourceConfig({ traceIdExpression: 'TraceId', statusCodeExpression: 'StatusCode', statusMessageExpression: 'StatusMessage', + ...(hasSpanEvents ? { spanEventsValueExpression: 'Events' } : {}), } : {}), }; } export function getDurationMsExpression(source: TSource) { - return `${source.durationExpression}/1e${(source.durationPrecision ?? 9) - 3}`; + return `(${source.durationExpression})/1e${(source.durationPrecision ?? 9) - 3}`; } export function getDurationSecondsExpression(source: TSource) { - return `${source.durationExpression}/1e${source.durationPrecision ?? 9}`; + return `(${source.durationExpression})/1e${source.durationPrecision ?? 9}`; } const ReqMetricTableColumns = { diff --git a/packages/common-utils/src/renderChartConfig.ts b/packages/common-utils/src/renderChartConfig.ts index efcc24d4b..dd6a7f576 100644 --- a/packages/common-utils/src/renderChartConfig.ts +++ b/packages/common-utils/src/renderChartConfig.ts @@ -251,7 +251,7 @@ const fastifySQL = ({ return parser.sqlify(ast); } catch (e) { - console.error('[renderWhereExpression]feat: Failed to parse SQL AST', e); + console.debug('[renderWhereExpression]feat: Failed to parse SQL AST', e); return rawSQL; } }; diff --git a/packages/common-utils/src/types.ts b/packages/common-utils/src/types.ts index 6f92f22f2..89e998fe8 100644 --- a/packages/common-utils/src/types.ts +++ b/packages/common-utils/src/types.ts @@ -510,6 +510,7 @@ export const SourceSchema = z.object({ durationPrecision: z.number().min(0).max(9).optional(), parentSpanIdExpression: z.string().optional(), spanNameExpression: z.string().optional(), + spanEventsValueExpression: z.string().optional(), spanKindExpression: z.string().optional(), statusCodeExpression: z.string().optional(),