From 1ea8faafc2902547d127edb742c0a45ce37a8bda Mon Sep 17 00:00:00 2001 From: Bence Szikora <1381651+benceszikora@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:41:28 -0500 Subject: [PATCH 1/5] display kind, status, instrumentation library, links, events and state data for traces --- .../configEditor/TracesConfig.test.tsx | 432 ++++++++++++++++++ src/components/configEditor/TracesConfig.tsx | 88 +++- .../queryBuilder/views/TraceQueryBuilder.tsx | 126 ++++- src/data/CHDatasource.ts | 8 + src/data/sqlGenerator.test.ts | 88 +++- src/data/sqlGenerator.ts | 52 ++- src/labels.ts | 73 ++- src/otel.ts | 7 + src/types/config.ts | 8 + src/types/queryBuilder.ts | 7 + src/views/CHConfigEditor.tsx | 8 + 11 files changed, 873 insertions(+), 24 deletions(-) diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx index 9f97c0c1..2f946367 100644 --- a/src/components/configEditor/TracesConfig.test.tsx +++ b/src/components/configEditor/TracesConfig.test.tsx @@ -24,6 +24,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -48,6 +56,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -79,6 +95,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -110,6 +134,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -140,6 +172,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -171,6 +211,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -202,6 +250,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -233,6 +289,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -264,6 +328,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -295,6 +367,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -326,6 +406,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -357,6 +445,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -388,6 +484,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={onStartTimeColumnChange} onTagsColumnChange={() => {}} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -419,6 +523,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={onTagsColumnChange} onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -450,6 +562,14 @@ describe('TracesConfig', () => { onStartTimeColumnChange={() => {}} onTagsColumnChange={() => {}} onServiceTagsColumnChange={onServiceTagsColumnChange} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -461,4 +581,316 @@ describe('TracesConfig', () => { expect(onServiceTagsColumnChange).toHaveBeenCalledTimes(1); expect(onServiceTagsColumnChange).toHaveBeenCalledWith('changed'); }); + + it('should call onKindColumnChange when changed', () => { + const onKindColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={onKindColumnChange} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.kind.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onKindColumnChange).toHaveBeenCalledTimes(1); + expect(onKindColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onStatusCodeColumnChange when changed', () => { + const onStatusCodeColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={onStatusCodeColumnChange} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.statusCode.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onStatusCodeColumnChange).toHaveBeenCalledTimes(1); + expect(onStatusCodeColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onStatusMessageColumnChange when changed', () => { + const onStatusMessageColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={onStatusMessageColumnChange} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.statusMessage.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onStatusMessageColumnChange).toHaveBeenCalledTimes(1); + expect(onStatusMessageColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onInstrumentationLibraryNameColumnChange when changed', () => { + const onInstrumentationLibraryNameColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.instrumentationLibraryName.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onInstrumentationLibraryNameColumnChange).toHaveBeenCalledTimes(1); + expect(onInstrumentationLibraryNameColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onInstrumentationLibraryVersionColumnChange when changed', () => { + const onInstrumentationLibraryVersionColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.instrumentationLibraryVersion.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledTimes(1); + expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onStateColumnChange when changed', () => { + const onStateColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={onStateColumnChange} + onEventsColumnChange={() => {}} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onStateColumnChange).toHaveBeenCalledTimes(1); + expect(onStateColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onEventsColumnChange when changed', () => { + const onEventsColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={onEventsColumnChange} + onLinksColumnChange={() => {}} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.events.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onEventsColumnChange).toHaveBeenCalledTimes(1); + expect(onEventsColumnChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onLinksColumnChange when changed', () => { + const onLinksColumnChange = jest.fn(); + const result = render( + <TracesConfig + tracesConfig={{}} + onDefaultDatabaseChange={() => {}} + onDefaultTableChange={() => {}} + onOtelEnabledChange={() => {}} + onOtelVersionChange={() => {}} + onTraceIdColumnChange={() => {}} + onSpanIdColumnChange={() => {}} + onOperationNameColumnChange={() => {}} + onParentSpanIdColumnChange={() => {}} + onServiceNameColumnChange={() => {}} + onDurationColumnChange={() => {}} + onDurationUnitChange={() => {}} + onStartTimeColumnChange={() => {}} + onTagsColumnChange={() => {}} + onServiceTagsColumnChange={() => {}} + onKindColumnChange={() => {}} + onStatusCodeColumnChange={() => {}} + onStatusMessageColumnChange={() => {}} + onInstrumentationLibraryNameColumnChange={() => {}} + onInstrumentationLibraryVersionColumnChange={() => {}} + onStateColumnChange={() => {}} + onEventsColumnChange={() => {}} + onLinksColumnChange={onLinksColumnChange} + /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.links.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onLinksColumnChange).toHaveBeenCalledTimes(1); + expect(onLinksColumnChange).toHaveBeenCalledWith('changed'); + }); }); diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx index 27288042..8cf563a1 100644 --- a/src/components/configEditor/TracesConfig.tsx +++ b/src/components/configEditor/TracesConfig.tsx @@ -27,6 +27,14 @@ interface TraceConfigProps { onStartTimeColumnChange: (v: string) => void; onTagsColumnChange: (v: string) => void; onServiceTagsColumnChange: (v: string) => void; + onKindColumnChange: (v: string) => void; + onStatusCodeColumnChange: (v: string) => void; + onStatusMessageColumnChange: (v: string) => void; + onInstrumentationLibraryNameColumnChange: (v: string) => void; + onInstrumentationLibraryVersionColumnChange: (v: string) => void; + onStateColumnChange: (v: string) => void; + onEventsColumnChange: (v: string) => void; + onLinksColumnChange: (v: string) => void; } export const TracesConfig = (props: TraceConfigProps) => { @@ -35,13 +43,17 @@ export const TracesConfig = (props: TraceConfigProps) => { onOtelEnabledChange, onOtelVersionChange, onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange, onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange, - onTagsColumnChange, onServiceTagsColumnChange, + onTagsColumnChange, onServiceTagsColumnChange, onKindColumnChange, onStatusCodeColumnChange, + onStatusMessageColumnChange, onInstrumentationLibraryNameColumnChange, onInstrumentationLibraryVersionColumnChange, + onStateColumnChange, onEventsColumnChange, onLinksColumnChange } = props; let { defaultDatabase, defaultTable, otelEnabled, otelVersion, traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn, - durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn + durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn, kindColumn, + statusCodeColumn, statusMessageColumn, instrumentationLibraryNameColumn, instrumentationLibraryVersionColumn, + stateColumn, eventsColumn, linksColumn } = (props.tracesConfig || {}) as CHTracesConfig; const labels = allLabels.components.Config.TracesConfig; @@ -56,6 +68,14 @@ export const TracesConfig = (props: TraceConfigProps) => { durationColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceDurationTime); tagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceTags); serviceTagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceTags); + kindColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceKind); + statusCodeColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusCode); + statusMessageColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusMessage); + instrumentationLibraryNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryName); + instrumentationLibraryVersionColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryVersion); + stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState); + eventsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceEvents); + linksColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceLinks); durationUnit = otelConfig.traceDurationUnit.toString(); } @@ -181,6 +201,70 @@ export const TracesConfig = (props: TraceConfigProps) => { value={serviceTagsColumn || ''} onChange={onServiceTagsColumnChange} /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.kind.label} + placeholder={columnLabelToPlaceholder(labels.columns.kind.label)} + tooltip={labels.columns.kind.tooltip} + value={kindColumn || ''} + onChange={onKindColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.statusCode.label} + placeholder={columnLabelToPlaceholder(labels.columns.statusCode.label)} + tooltip={labels.columns.statusCode.tooltip} + value={statusCodeColumn || ''} + onChange={onStatusCodeColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.statusMessage.label} + placeholder={columnLabelToPlaceholder(labels.columns.statusMessage.label)} + tooltip={labels.columns.statusMessage.tooltip} + value={statusMessageColumn || ''} + onChange={onStatusMessageColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.instrumentationLibraryName.label} + placeholder={columnLabelToPlaceholder(labels.columns.instrumentationLibraryName.label)} + tooltip={labels.columns.instrumentationLibraryName.tooltip} + value={instrumentationLibraryNameColumn || ''} + onChange={onInstrumentationLibraryNameColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.instrumentationLibraryVersion.label} + placeholder={columnLabelToPlaceholder(labels.columns.instrumentationLibraryVersion.label)} + tooltip={labels.columns.instrumentationLibraryVersion.tooltip} + value={instrumentationLibraryVersionColumn || ''} + onChange={onInstrumentationLibraryVersionColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.state.label} + placeholder={columnLabelToPlaceholder(labels.columns.state.label)} + tooltip={labels.columns.state.tooltip} + value={stateColumn || ''} + onChange={onStateColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.events.label} + placeholder={columnLabelToPlaceholder(labels.columns.events.label)} + tooltip={labels.columns.events.tooltip} + value={eventsColumn || ''} + onChange={onEventsColumnChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.links.label} + placeholder={columnLabelToPlaceholder(labels.columns.links.label)} + tooltip={labels.columns.links.tooltip} + value={linksColumn || ''} + onChange={onLinksColumnChange} + /> </ConfigSubSection> </ConfigSection> ); diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index d0824aa4..1097db45 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -38,6 +38,14 @@ interface TraceQueryBuilderState { durationUnit: TimeUnit; tagsColumn?: SelectedColumn; serviceTagsColumn?: SelectedColumn; + kindColumn?: SelectedColumn; + statusCodeColumn?: SelectedColumn; + statusMessageColumn?: SelectedColumn; + instrumentationLibraryNameColumn?: SelectedColumn; + instrumentationLibraryVersionColumn?: SelectedColumn; + stateColumn?: SelectedColumn; + eventsColumn?: SelectedColumn; + linksColumn?: SelectedColumn; traceId: string; orderBy: OrderBy[]; limit: number; @@ -66,6 +74,14 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { durationUnit: builderOptions.meta?.traceDurationUnit || TimeUnit.Nanoseconds, tagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceTags), serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags), + kindColumn: getColumnByHint(builderOptions, ColumnHint.TraceKind), + statusCodeColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusCode), + statusMessageColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusMessage), + instrumentationLibraryNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryName), + instrumentationLibraryVersionColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryVersion), + stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState), + eventsColumn: getColumnByHint(builderOptions, ColumnHint.TraceEvents), + linksColumn: getColumnByHint(builderOptions, ColumnHint.TraceLinks), traceId: builderOptions.meta?.traceId || '', orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 0, @@ -82,7 +98,15 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { next.startTimeColumn, next.durationTimeColumn, next.tagsColumn, - next.serviceTagsColumn + next.serviceTagsColumn, + next.kindColumn, + next.statusCodeColumn, + next.statusMessageColumn, + next.instrumentationLibraryNameColumn, + next.instrumentationLibraryVersionColumn, + next.stateColumn, + next.eventsColumn, + next.linksColumn, ].filter(c => c !== undefined) as SelectedColumn[]; builderOptionsDispatch(setOptions({ @@ -256,6 +280,106 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { inline /> </div> + <div className="gf-form"> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.kindColumn} + invalid={!builderState.kindColumn} + onColumnChange={onOptionChange('kindColumn')} + columnHint={ColumnHint.TraceKind} + label={labels.columns.kind.label} + tooltip={labels.columns.kind.tooltip} + wide + /> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.statusCodeColumn} + invalid={!builderState.statusCodeColumn} + onColumnChange={onOptionChange('statusCodeColumn')} + columnHint={ColumnHint.TraceStatusCode} + label={labels.columns.statusCode.label} + tooltip={labels.columns.statusCode.tooltip} + wide + inline + /> + </div> + <div className="gf-form"> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.statusMessageColumn} + invalid={!builderState.statusMessageColumn} + onColumnChange={onOptionChange('statusMessageColumn')} + columnHint={ColumnHint.TraceStatusMessage} + label={labels.columns.statusMessage.label} + tooltip={labels.columns.statusMessage.tooltip} + wide + /> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.instrumentationLibraryNameColumn} + invalid={!builderState.instrumentationLibraryNameColumn} + onColumnChange={onOptionChange('instrumentationLibraryNameColumn')} + columnHint={ColumnHint.TraceInstrumentationLibraryName} + label={labels.columns.instrumentationLibraryName.label} + tooltip={labels.columns.instrumentationLibraryName.tooltip} + wide + inline + /> + </div> + <div className="gf-form"> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.instrumentationLibraryVersionColumn} + invalid={!builderState.instrumentationLibraryVersionColumn} + onColumnChange={onOptionChange('instrumentationLibraryVersionColumn')} + columnHint={ColumnHint.TraceInstrumentationLibraryVersion} + label={labels.columns.instrumentationLibraryVersion.label} + tooltip={labels.columns.instrumentationLibraryVersion.tooltip} + wide + /> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.stateColumn} + invalid={!builderState.stateColumn} + onColumnChange={onOptionChange('stateColumn')} + columnHint={ColumnHint.TraceState} + label={labels.columns.state.label} + tooltip={labels.columns.state.tooltip} + wide + inline + /> + </div> + <div className="gf-form"> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.eventsColumn} + invalid={!builderState.eventsColumn} + onColumnChange={onOptionChange('eventsColumn')} + columnHint={ColumnHint.TraceEvents} + label={labels.columns.events.label} + tooltip={labels.columns.events.tooltip} + wide + /> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.linksColumn} + invalid={!builderState.linksColumn} + onColumnChange={onOptionChange('linksColumn')} + columnHint={ColumnHint.TraceLinks} + label={labels.columns.links.label} + tooltip={labels.columns.links.tooltip} + wide + inline + /> + </div> </Collapse> <Collapse label={labels.filtersSection} collapsible diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index ad41970b..396ad240 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -493,6 +493,14 @@ export class Datasource traceConfig.startTimeColumn && result.set(ColumnHint.Time, traceConfig.startTimeColumn); traceConfig.tagsColumn && result.set(ColumnHint.TraceTags, traceConfig.tagsColumn); traceConfig.serviceTagsColumn && result.set(ColumnHint.TraceServiceTags, traceConfig.serviceTagsColumn); + traceConfig.kindColumn && result.set(ColumnHint.TraceKind, traceConfig.kindColumn); + traceConfig.statusCodeColumn && result.set(ColumnHint.TraceStatusCode, traceConfig.statusCodeColumn); + traceConfig.statusMessageColumn && result.set(ColumnHint.TraceStatusMessage, traceConfig.statusMessageColumn); + traceConfig.instrumentationLibraryNameColumn && result.set(ColumnHint.TraceInstrumentationLibraryName, traceConfig.instrumentationLibraryNameColumn); + traceConfig.instrumentationLibraryVersionColumn && result.set(ColumnHint.TraceInstrumentationLibraryVersion, traceConfig.instrumentationLibraryVersionColumn); + traceConfig.stateColumn && result.set(ColumnHint.TraceState, traceConfig.stateColumn); + traceConfig.eventsColumn && result.set(ColumnHint.TraceEvents, traceConfig.eventsColumn); + traceConfig.linksColumn && result.set(ColumnHint.TraceLinks, traceConfig.linksColumn); return result; } diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index fe27fcb3..f35fd0e6 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -8,9 +8,9 @@ describe('SQL Generator', () => { table: 'sample', queryType: QueryType.Table, columns: [ - { name: 'a', type: 'UInt64' }, - { name: 'b', type: 'String' }, - { name: 'c', type: 'String' }, + { name: 'a', type: 'UInt64' }, + { name: 'b', type: 'String' }, + { name: 'c', type: 'String' }, ], limit: 1000, filters: [ @@ -41,9 +41,9 @@ describe('SQL Generator', () => { queryType: QueryType.Table, mode: BuilderMode.Aggregate, columns: [ - { name: 'a', type: 'DateTime' }, - { name: 'b', type: 'String' }, - { name: 'c', type: 'String' }, + { name: 'a', type: 'DateTime' }, + { name: 'b', type: 'String' }, + { name: 'c', type: 'String' }, ], aggregates: [ { aggregateType: AggregateType.Count, column: '*', alias: 'd' } @@ -77,9 +77,9 @@ describe('SQL Generator', () => { table: 'logs', queryType: QueryType.Logs, columns: [ - { name: 'log_ts', type: 'DateTime', hint: ColumnHint.Time }, - { name: 'log_level', type: 'String', hint: ColumnHint.LogLevel }, - { name: 'log_body', type: 'String', hint: ColumnHint.LogMessage }, + { name: 'log_ts', type: 'DateTime', hint: ColumnHint.Time }, + { name: 'log_level', type: 'String', hint: ColumnHint.LogLevel }, + { name: 'log_body', type: 'String', hint: ColumnHint.LogMessage }, ], limit: 1000, filters: [ @@ -122,8 +122,8 @@ describe('SQL Generator', () => { table: 'time_data', queryType: QueryType.TimeSeries, columns: [ - { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time }, - { name: 'number_field', type: 'UInt64' }, + { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time }, + { name: 'number_field', type: 'UInt64' }, ], limit: 100, filters: [ @@ -154,8 +154,8 @@ describe('SQL Generator', () => { table: 'time_data', queryType: QueryType.TimeSeries, columns: [ - { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time }, - { name: 'number_field', type: 'UInt64' }, + { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time }, + { name: 'number_field', type: 'UInt64' }, ], limit: 100, aggregates: [{ aggregateType: AggregateType.Sum, column: 'number_field', alias: 'total' }], @@ -226,6 +226,68 @@ describe('SQL Generator', () => { expect(sql).toEqual(expectedSqlParts.join(' ')); }); + it('generates trace ID query with additional fields (kind, statusCode, statusMessage, instrumentationLibraryName, instrumentationLibraryVersion, traceState, logs, references) and OTel enabled', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'otel_traces', + queryType: QueryType.Traces, + columns: [ + { name: 'TraceId', type: 'String', hint: ColumnHint.TraceId }, + { name: 'SpanId', type: 'String', hint: ColumnHint.TraceSpanId }, + { name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId }, + { name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName }, + { name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName }, + { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time }, + { name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime }, + { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags }, + { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags }, + { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode }, + { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind }, + { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage }, + { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName }, + { name: 'InstrumentationLibraryVersion', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryVersion }, + { name: 'TraceState', type: 'String', hint: ColumnHint.TraceState }, + { name: 'Events', type: 'Array(Tuple(name String, timestamp UInt64, attributes Map(String, String)))', hint: ColumnHint.TraceEvents }, + { name: 'Links', type: 'Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String)))', hint: ColumnHint.TraceLinks }, + ], + filters: [], + meta: { + minimized: true, + otelEnabled: true, + otelVersion: 'latest', + traceDurationUnit: TimeUnit.Nanoseconds, + isTraceIdMode: true, + traceId: 'abcdefg' + }, + limit: 1000, + orderBy: [] + }; + + const expectedSqlParts = [ + `WITH 'abcdefg' as trace_id, (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_start,`, + `(SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_end`, + 'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,', + '"ServiceName" as serviceName, "SpanName" as operationName, multiply(toUnixTimestamp64Nano("Timestamp"), 0.000001) as startTime,', + 'multiply("Duration", 0.000001) as duration,', + `arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`, + `mapKeys("SpanAttributes")) as tags,`, + `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`, + `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`, + '"Kind" as kind,', + '"StatusMessage" as statusMessage,', + '"InstrumentationLibraryName" as instrumentationLibraryName,', + '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,', + '"TraceState" as traceState,', + `arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`, + `arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`, + `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`, + 'LIMIT 1000' + ]; + + const sql = generateSql(opts); + expect(sql).toEqual(expectedSqlParts.join(' ')); + }); + it('generates trace ID query with OTel enabled', () => { const opts: QueryBuilderOptions = { database: 'default', diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index dfdab346..819c9ce5 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -149,6 +149,44 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => { if (traceStatusCode !== undefined) { selectParts.push(`if(${escapeIdentifier(traceStatusCode.name)} IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode`); } + + const traceKind = getColumnByHint(options, ColumnHint.TraceKind); + if (traceKind !== undefined) { + selectParts.push(`${escapeIdentifier(traceKind.name)} as kind`); + } + + const traceStatusMessage = getColumnByHint(options, ColumnHint.TraceStatusMessage); + if (traceStatusMessage !== undefined) { + selectParts.push(`${escapeIdentifier(traceStatusMessage.name)} as statusMessage`); + } + + const traceInstrumentationLibraryName = getColumnByHint(options, ColumnHint.TraceInstrumentationLibraryName); + if (traceInstrumentationLibraryName !== undefined) { + selectParts.push(`${escapeIdentifier(traceInstrumentationLibraryName.name)} as instrumentationLibraryName`); + } + + const traceInstrumentationLibraryVersion = getColumnByHint(options, ColumnHint.TraceInstrumentationLibraryVersion); + if (traceInstrumentationLibraryVersion !== undefined) { + selectParts.push(`${escapeIdentifier(traceInstrumentationLibraryVersion.name)} as instrumentationLibraryVersion`); + } + + const traceState = getColumnByHint(options, ColumnHint.TraceState); + if (traceState !== undefined) { + selectParts.push(`${escapeIdentifier(traceState.name)} as traceState`); + } + + const traceEvents = getColumnByHint(options, ColumnHint.TraceEvents); + if (traceEvents !== undefined) { + // Assumes `events` is of type Array(Tuple(name String, timestamp UInt64, attributes Map(String, String))) + selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`); + } + + const traceLinks = getColumnByHint(options, ColumnHint.TraceLinks); + if (traceLinks !== undefined) { + // Assumes `links` is of type Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String))) + selectParts.push(`arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`); + } + const selectPartsSql = selectParts.join(', '); // Optimize trace ID filtering for OTel enabled trace lookups @@ -689,7 +727,7 @@ const getFilters = (options: QueryBuilderOptions): string => { operator = ''; negate = true; } else if (filter.operator === FilterOperator.WithInGrafanaTimeRange) { - operator = ''; + operator = ''; } if (operator) { @@ -758,11 +796,11 @@ const getFilters = (options: QueryBuilderOptions): string => { }; const stripTypeModifiers = (type: string): string => { - return type.toLowerCase(). - replace(/\(/g, ''). - replace(/\)/g, ''). - replace(/nullable/g, ''). - replace(/lowcardinality/g, ''); + return type.toLowerCase(). + replace(/\(/g, ''). + replace(/\)/g, ''). + replace(/nullable/g, ''). + replace(/lowcardinality/g, ''); } const isBooleanType = (type: string): boolean => (type?.toLowerCase().startsWith('boolean')); @@ -773,7 +811,7 @@ const isDateType = (type: string): boolean => type?.toLowerCase().startsWith('da const isStringType = (type: string): boolean => { type = stripTypeModifiers(type.toLowerCase()); return (type === 'string' || type.startsWith('fixedstring')) - && !(isBooleanType(type) || isNumberType(type) || isDateType(type)); + && !(isBooleanType(type) || isNumberType(type) || isDateType(type)); } const isNullFilter = (operator: FilterOperator): boolean => operator === FilterOperator.IsNull || operator === FilterOperator.IsNotNull; const isBooleanFilter = (type: string): boolean => isBooleanType(type); diff --git a/src/labels.ts b/src/labels.ts index 02a7f405..9b6a7447 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -185,7 +185,39 @@ export default { serviceTags: { label: 'Service Tags column', tooltip: 'Column for the service tags' - } + }, + kind: { + label: 'Kind column', + tooltip: 'Column for the trace kind' + }, + statusCode: { + label: 'Status Code column', + tooltip: 'Column for the trace status code' + }, + statusMessage: { + label: 'Status Message column', + tooltip: 'Column for the trace status message' + }, + instrumentationLibraryName: { + label: 'Library Name column', + tooltip: 'Column for the instrumentation library name' + }, + instrumentationLibraryVersion: { + label: 'Library Version column', + tooltip: 'Column for the instrumentation library version' + }, + state: { + label: 'State column', + tooltip: 'Column for the trace state' + }, + events: { + label: 'Events column', + tooltip: 'Column for the trace events' + }, + links: { + label: 'Links column', + tooltip: 'Column for the trace references' + }, } }, LogsConfig: { @@ -403,6 +435,38 @@ export default { label: 'Service Tags Column', tooltip: 'Column that contains the service tags' }, + kind: { + label: 'Kind Column', + tooltip: 'Column that contains the trace kind' + }, + statusCode: { + label: 'Status Code Column', + tooltip: 'Column that contains the trace status code' + }, + statusMessage: { + label: 'Status Message Column', + tooltip: 'Column that contains the trace status message' + }, + instrumentationLibraryName: { + label: 'Library Name Column', + tooltip: 'Column that contains the instrumentation library name' + }, + instrumentationLibraryVersion: { + label: 'Library Version Column', + tooltip: 'Column that contains the instrumentation library version' + }, + state: { + label: 'State Column', + tooltip: 'Column that contains the trace state' + }, + events: { + label: 'Events Column', + tooltip: 'Column that contains the trace events' + }, + links: { + label: 'Links Column', + tooltip: 'Column that contains the trace references' + }, traceIdFilter: { label: 'Trace ID', tooltip: 'filter by a specific trace ID' @@ -437,6 +501,13 @@ export default { [ColumnHint.TraceTags]: 'Tags', [ColumnHint.TraceServiceTags]: 'Service Tags', [ColumnHint.TraceStatusCode]: 'Status Code', + [ColumnHint.TraceKind]: 'Kind', + [ColumnHint.TraceStatusMessage]: 'Status Message', + [ColumnHint.TraceInstrumentationLibraryName]: 'Instrumentation Library Name', + [ColumnHint.TraceInstrumentationLibraryVersion]: 'Instrumentation Library Version', + [ColumnHint.TraceState]: 'State', + [ColumnHint.TraceEvents]: 'Events', + [ColumnHint.TraceLinks]: 'Links', } } } diff --git a/src/otel.ts b/src/otel.ts index 7af421ef..57f6d825 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -49,6 +49,13 @@ const otel129: OtelVersion = { [ColumnHint.TraceTags, 'SpanAttributes'], [ColumnHint.TraceServiceTags, 'ResourceAttributes'], [ColumnHint.TraceStatusCode, 'StatusCode'], + [ColumnHint.TraceKind, 'SpanKind'], + [ColumnHint.TraceStatusMessage, 'StatusMessage'], + [ColumnHint.TraceInstrumentationLibraryName, 'InstrumentationLibraryName'], + [ColumnHint.TraceInstrumentationLibraryVersion, 'InstrumentationLibraryVersion'], + [ColumnHint.TraceState, 'State'], + [ColumnHint.TraceEvents, 'Events'], + [ColumnHint.TraceLinks, 'Links'], ]), traceDurationUnit: TimeUnit.Nanoseconds, }; diff --git a/src/types/config.ts b/src/types/config.ts index 3f976bb6..034e254e 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -90,6 +90,14 @@ export interface CHTracesConfig { startTimeColumn?: string; tagsColumn?: string; serviceTagsColumn?: string; + kindColumn?: string; + statusCodeColumn?: string; + statusMessageColumn?: string; + instrumentationLibraryNameColumn?: string; + instrumentationLibraryVersionColumn?: string; + stateColumn?: string; + eventsColumn?: string; + linksColumn?: string; } export interface AliasTableEntry { diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index 54cdb335..756723a5 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -133,6 +133,13 @@ export enum ColumnHint { TraceTags = 'trace_tags', TraceServiceTags = 'trace_service_tags', TraceStatusCode = 'trace_status_code', + TraceKind = 'trace_kind', + TraceStatusMessage = 'trace_status_message', + TraceInstrumentationLibraryName = 'instrumentation_library_name', + TraceInstrumentationLibraryVersion = 'instrumentation_library_version', + TraceState = 'trace_state', + TraceEvents = 'trace_events', + TraceLinks = 'trace_links', } /** diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index f14cd0c1..d76dceff 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -444,6 +444,14 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { onStartTimeColumnChange={c => onTracesConfigChange('startTimeColumn', c)} onTagsColumnChange={c => onTracesConfigChange('tagsColumn', c)} onServiceTagsColumnChange={c => onTracesConfigChange('serviceTagsColumn', c)} + onKindColumnChange={c => onTracesConfigChange('kindColumn', c)} + onStatusCodeColumnChange={c => onTracesConfigChange('statusCodeColumn', c)} + onStatusMessageColumnChange={c => onTracesConfigChange('statusMessageColumn', c)} + onInstrumentationLibraryNameColumnChange={c => onTracesConfigChange('instrumentationLibraryNameColumn', c)} + onInstrumentationLibraryVersionColumnChange={c => onTracesConfigChange('instrumentationLibraryVersionColumn', c)} + onStateColumnChange={c => onTracesConfigChange('stateColumn', c)} + onEventsColumnChange={c => onTracesConfigChange('eventsColumn', c)} + onLinksColumnChange={c => onTracesConfigChange('linksColumn', c)} /> <Divider /> From e7f9977b300caf079bcf90ea54d78dea2d997626 Mon Sep 17 00:00:00 2001 From: Harry Phillips <harryjamesphillips@gmail.com> Date: Mon, 3 Feb 2025 05:37:43 -0600 Subject: [PATCH 2/5] Match OTel Collector Schema --- src/data/sqlGenerator.test.ts | 4 ++-- src/data/sqlGenerator.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index f35fd0e6..e673ab5c 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -278,8 +278,8 @@ describe('SQL Generator', () => { '"InstrumentationLibraryName" as instrumentationLibraryName,', '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,', '"TraceState" as traceState,', - `arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`, - `arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`, + `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`, + `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`, `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`, 'LIMIT 1000' ]; diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 819c9ce5..e360aebb 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -177,14 +177,14 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => { const traceEvents = getColumnByHint(options, ColumnHint.TraceEvents); if (traceEvents !== undefined) { - // Assumes `events` is of type Array(Tuple(name String, timestamp UInt64, attributes Map(String, String))) - selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`); + // Assumes `events` is of type Nested(Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String)) + selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`); } const traceLinks = getColumnByHint(options, ColumnHint.TraceLinks); if (traceLinks !== undefined) { - // Assumes `links` is of type Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String))) - selectParts.push(`arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`); + // Assumes `links` is of type Nested(TraceId String, SpanId String, TraceState String, Attributes Map(LowCardinality(String), String)) + selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`); } const selectPartsSql = selectParts.join(', '); From 5d9779ba96ed4ca1b1e914989ba5054a10412566 Mon Sep 17 00:00:00 2001 From: Harry Phillips <harryjamesphillips@gmail.com> Date: Mon, 3 Feb 2025 10:49:58 -0600 Subject: [PATCH 3/5] Fix events and links column entry fields --- .../queryBuilder/views/TraceQueryBuilder.tsx | 46 ++++++++++++------- src/labels.ts | 4 +- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index 5b2e4115..ff2318fa 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -124,6 +124,18 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { })); }, builderState); + // A function to assemble custom column definitions for the prefixed Events/Links columns + const onColumnPrefixChange = (name: keyof TraceQueryBuilderState, columnHint: ColumnHint) => { + const baseOptionChange = onOptionChange(name); + return (name: string) => { + baseOptionChange({ + name: name, + hint: columnHint, + custom: true, + }); + }; + }; + useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch); useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch); useDefaultFilters(builderOptions.table, builderState.isTraceIdMode, isNewQuery, builderOptionsDispatch); @@ -282,22 +294,6 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { inline /> </div> - <div className="gf-form"> - <LabeledInput - disabled={builderState.otelEnabled} - label={labels.columns.eventsPrefix.label} - tooltip={labels.columns.eventsPrefix.tooltip} - value={builderState.eventsColumnPrefix?.name || ''} - onChange={onOptionChange('eventsColumnPrefix')} - /> - <LabeledInput - disabled={builderState.otelEnabled} - label={labels.columns.linksPrefix.label} - tooltip={labels.columns.linksPrefix.tooltip} - value={builderState.linksColumnPrefix?.name || ''} - onChange={onOptionChange('linksColumnPrefix')} - /> - </div> <div className="gf-form"> <ColumnSelect disabled={builderState.otelEnabled} @@ -373,6 +369,24 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { inline /> </div> + <div className="gf-form"> + <LabeledInput + disabled={builderState.otelEnabled} + label={labels.columns.eventsPrefix.label} + tooltip={labels.columns.eventsPrefix.tooltip} + value={builderState.eventsColumnPrefix?.name || ''} + onChange={onColumnPrefixChange('eventsColumnPrefix', ColumnHint.TraceEventsPrefix)} + /> + </div> + <div className="gf-form"> + <LabeledInput + disabled={builderState.otelEnabled} + label={labels.columns.linksPrefix.label} + tooltip={labels.columns.linksPrefix.tooltip} + value={builderState.linksColumnPrefix?.name || ''} + onChange={onColumnPrefixChange('linksColumnPrefix', ColumnHint.TraceLinksPrefix)} + /> + </div> </Collapse> <Collapse label={labels.filtersSection} collapsible diff --git a/src/labels.ts b/src/labels.ts index 6c57ab61..1189433c 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -436,11 +436,11 @@ export default { tooltip: 'Column that contains the service tags' }, eventsPrefix: { - label: 'Events Prefix Column', + label: 'Events Prefix', tooltip: 'Prefix for the events column' }, linksPrefix: { - label: 'Links Column', + label: 'Links Prefix', tooltip: 'Prefix for the trace references column' }, kind: { From c208504c0b4b6f29eff8b05dafb6045ae49fa300 Mon Sep 17 00:00:00 2001 From: Harry Phillips <harryjamesphillips@gmail.com> Date: Mon, 3 Feb 2025 10:50:15 -0600 Subject: [PATCH 4/5] Fix events and links queries when using flatten_nested=0 --- src/data/sqlGenerator.test.ts | 4 ++-- src/data/sqlGenerator.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index 01057070..ab809138 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -273,8 +273,8 @@ describe('SQL Generator', () => { `mapKeys("SpanAttributes")) as tags,`, `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`, `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`, - `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),"Events".Name, "Events".Timestamp, "Events".Attributes) AS logs,`, - `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links".TraceId, "Links".SpanId, "Links".Attributes) AS references,`, + `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`, + `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references,`, '"Kind" as kind,', '"StatusMessage" as statusMessage,', '"InstrumentationLibraryName" as instrumentationLibraryName,', diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 9c45aab1..84e32be4 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -152,12 +152,16 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => { const traceEventsPrefix = getColumnByHint(options, ColumnHint.TraceEventsPrefix); if (traceEventsPrefix !== undefined) { - selectParts.push(`arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),${escapeIdentifier(traceEventsPrefix.name)}.Name, ${escapeIdentifier(traceEventsPrefix.name)}.Timestamp, ${escapeIdentifier(traceEventsPrefix.name)}.Attributes) AS logs`); + // It is important to treat the prefixed columns as one nested column, as this + // ensures that the query will work when the column was created with flatten_nested. + selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEventsPrefix.name)}) as logs`); } const traceLinksPrefix = getColumnByHint(options, ColumnHint.TraceLinksPrefix); if (traceLinksPrefix !== undefined) { - selectParts.push(`arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}.TraceId, ${escapeIdentifier(traceLinksPrefix.name)}.SpanId, ${escapeIdentifier(traceLinksPrefix.name)}.Attributes) AS references`); + // It is important to treat the prefixed columns as one nested column, as this + // ensures that the query will work when the column was created with flatten_nested. + selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}) AS references`); } const traceKind = getColumnByHint(options, ColumnHint.TraceKind); From 3947bccfd83259b57117131b56bab82d0844345c Mon Sep 17 00:00:00 2001 From: Spencer Torres <contact@spencertorres.com> Date: Wed, 26 Mar 2025 01:38:17 -0400 Subject: [PATCH 5/5] code review edits --- .../configEditor/TracesConfig.test.tsx | 709 +++--------------- src/components/configEditor/TracesConfig.tsx | 82 +- src/components/queryBuilder/Switch.tsx | 4 +- .../queryBuilder/views/TraceQueryBuilder.tsx | 84 ++- .../views/traceQueryBuilderHooks.test.ts | 10 +- .../views/traceQueryBuilderHooks.ts | 13 +- src/data/CHDatasource.ts | 14 +- src/data/sqlGenerator.test.ts | 72 +- src/data/sqlGenerator.ts | 50 +- src/labels.ts | 22 +- src/otel.ts | 12 +- src/types/config.ts | 8 +- src/types/queryBuilder.ts | 10 +- src/views/CHConfigEditor.tsx | 17 +- 14 files changed, 376 insertions(+), 731 deletions(-) diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx index 6e9f601a..8afb5224 100644 --- a/src/components/configEditor/TracesConfig.test.tsx +++ b/src/components/configEditor/TracesConfig.test.tsx @@ -1,38 +1,43 @@ import React from 'react'; import { render, fireEvent } from '@testing-library/react'; -import { TracesConfig } from './TracesConfig'; +import { TracesConfig, TraceConfigProps } from './TracesConfig'; import allLabels from 'labels'; import { columnLabelToPlaceholder } from 'data/utils'; import { defaultTraceTable } from 'otel'; +function defaultTraceConfigProps(): TraceConfigProps { + return { + tracesConfig: {}, + onDefaultDatabaseChange: () => {}, + onDefaultTableChange: () => {}, + onOtelEnabledChange: () => {}, + onOtelVersionChange: () => {}, + onTraceIdColumnChange: () => {}, + onSpanIdColumnChange: () => {}, + onOperationNameColumnChange: () => {}, + onParentSpanIdColumnChange: () => {}, + onServiceNameColumnChange: () => {}, + onDurationColumnChange: () => {}, + onDurationUnitChange: () => {}, + onStartTimeColumnChange: () => {}, + onTagsColumnChange: () => {}, + onServiceTagsColumnChange: () => {}, + onKindColumnChange: () => {}, + onStatusCodeColumnChange: () => {}, + onStatusMessageColumnChange: () => {}, + onStateColumnChange: () => {}, + onInstrumentationLibraryNameColumnChange: () => {}, + onInstrumentationLibraryVersionColumnChange: () => {}, + onFlattenNestedChange: () => {}, + onEventsColumnPrefixChange: () => {}, + onLinksColumnPrefixChange: () => {} + }; +} + describe('TracesConfig', () => { it('should render', () => { const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} /> ); expect(result.container.firstChild).not.toBeNull(); }); @@ -40,31 +45,7 @@ describe('TracesConfig', () => { it('should call onDefaultDatabase when changed', () => { const onDefaultDatabaseChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={onDefaultDatabaseChange} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onDefaultDatabaseChange={onDefaultDatabaseChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -79,31 +60,7 @@ describe('TracesConfig', () => { it('should call onDefaultTable when changed', () => { const onDefaultTableChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={onDefaultTableChange} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onDefaultTableChange={onDefaultTableChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -115,38 +72,14 @@ describe('TracesConfig', () => { expect(onDefaultTableChange).toHaveBeenCalledWith('changed'); }); - it('should call onOtelEnabled when changed', () => { + it('should call onOtelEnabled when changed', async () => { const onOtelEnabledChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={onOtelEnabledChange} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onOtelEnabledChange={onOtelEnabledChange} /> ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByRole('checkbox'); + const input = (await result.findAllByRole('checkbox'))[0]; expect(input).toBeInTheDocument(); fireEvent.click(input); expect(onOtelEnabledChange).toHaveBeenCalledTimes(1); @@ -157,29 +90,9 @@ describe('TracesConfig', () => { const onOtelVersionChange = jest.fn(); const result = render( <TracesConfig + {...defaultTraceConfigProps()} tracesConfig={{ otelEnabled: true }} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} onOtelVersionChange={onOtelVersionChange} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -195,31 +108,7 @@ describe('TracesConfig', () => { it('should call onTraceIdColumnChange when changed', () => { const onTraceIdColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={onTraceIdColumnChange} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onTraceIdColumnChange={onTraceIdColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -234,31 +123,7 @@ describe('TracesConfig', () => { it('should call onSpanIdColumnChange when changed', () => { const onSpanIdColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={onSpanIdColumnChange} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onSpanIdColumnChange={onSpanIdColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -273,31 +138,7 @@ describe('TracesConfig', () => { it('should call onOperationNameColumnChange when changed', () => { const onOperationNameColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={onOperationNameColumnChange} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onOperationNameColumnChange={onOperationNameColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -312,31 +153,7 @@ describe('TracesConfig', () => { it('should call onParentSpanIdColumnChange when changed', () => { const onParentSpanIdColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={onParentSpanIdColumnChange} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onParentSpanIdColumnChange={onParentSpanIdColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -351,31 +168,7 @@ describe('TracesConfig', () => { it('should call onServiceNameColumnChange when changed', () => { const onServiceNameColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={onServiceNameColumnChange} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onServiceNameColumnChange={onServiceNameColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -390,31 +183,7 @@ describe('TracesConfig', () => { it('should call onDurationColumnChange when changed', () => { const onDurationColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={onDurationColumnChange} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onDurationColumnChange={onDurationColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -429,31 +198,7 @@ describe('TracesConfig', () => { it('should call onDurationUnitChange when changed', () => { const onDurationUnitChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={onDurationUnitChange} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onDurationUnitChange={onDurationUnitChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -468,31 +213,7 @@ describe('TracesConfig', () => { it('should call onStartTimeColumnChange when changed', () => { const onStartTimeColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={onStartTimeColumnChange} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onStartTimeColumnChange={onStartTimeColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -507,31 +228,7 @@ describe('TracesConfig', () => { it('should call onTagsColumnChange when changed', () => { const onTagsColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={onTagsColumnChange} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onTagsColumnChange={onTagsColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -546,31 +243,7 @@ describe('TracesConfig', () => { it('should call onServiceTagsColumnChange when changed', () => { const onServiceTagsColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={onServiceTagsColumnChange} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onServiceTagsColumnChange={onServiceTagsColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -582,112 +255,10 @@ describe('TracesConfig', () => { expect(onServiceTagsColumnChange).toHaveBeenCalledWith('changed'); }); - it('should call onEventsColumnPrefixChange when changed', () => { - const onEventsColumnPrefixChange = jest.fn(); - const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={onEventsColumnPrefixChange} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> - ); - expect(result.container.firstChild).not.toBeNull(); - - const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.eventsPrefix.label)); - expect(input).toBeInTheDocument(); - fireEvent.change(input, { target: { value: 'changed' } }); - fireEvent.blur(input); - expect(onEventsColumnPrefixChange).toHaveBeenCalledTimes(1); - expect(onEventsColumnPrefixChange).toHaveBeenCalledWith('changed'); - }); - - it('should call onLinksColumnPrefixChange when changed', () => { - const onLinksColumnPrefixChange = jest.fn(); - const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={onLinksColumnPrefixChange} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> - ); - expect(result.container.firstChild).not.toBeNull(); - - const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.linksPrefix.label)); - expect(input).toBeInTheDocument(); - fireEvent.change(input, { target: { value: 'changed' } }); - fireEvent.blur(input); - expect(onLinksColumnPrefixChange).toHaveBeenCalledTimes(1); - expect(onLinksColumnPrefixChange).toHaveBeenCalledWith('changed'); - }); - it('should call onKindColumnChange when changed', () => { const onKindColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={onKindColumnChange} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onKindColumnChange={onKindColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -702,31 +273,7 @@ describe('TracesConfig', () => { it('should call onStatusCodeColumnChange when changed', () => { const onStatusCodeColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={onStatusCodeColumnChange} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onStatusCodeColumnChange={onStatusCodeColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -741,31 +288,7 @@ describe('TracesConfig', () => { it('should call onStatusMessageColumnChange when changed', () => { const onStatusMessageColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={onStatusMessageColumnChange} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onStatusMessageColumnChange={onStatusMessageColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -777,34 +300,25 @@ describe('TracesConfig', () => { expect(onStatusMessageColumnChange).toHaveBeenCalledWith('changed'); }); + it('should call onStateColumnChange when changed', () => { + const onStateColumnChange = jest.fn(); + const result = render( + <TracesConfig {...defaultTraceConfigProps()} onStateColumnChange={onStateColumnChange} /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onStateColumnChange).toHaveBeenCalledTimes(1); + expect(onStateColumnChange).toHaveBeenCalledWith('changed'); + }); + it('should call onInstrumentationLibraryNameColumnChange when changed', () => { const onInstrumentationLibraryNameColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -819,31 +333,7 @@ describe('TracesConfig', () => { it('should call onInstrumentationLibraryVersionColumnChange when changed', () => { const onInstrumentationLibraryVersionColumnChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange} - onStateColumnChange={() => {}} - /> + <TracesConfig {...defaultTraceConfigProps()} onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange} /> ); expect(result.container.firstChild).not.toBeNull(); @@ -855,42 +345,47 @@ describe('TracesConfig', () => { expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledWith('changed'); }); - it('should call onStateColumnChange when changed', () => { - const onStateColumnChange = jest.fn(); + it('should call onFlattenNestedChange when changed', async () => { + const onFlattenNestedChange = jest.fn(); const result = render( - <TracesConfig - tracesConfig={{}} - onDefaultDatabaseChange={() => {}} - onDefaultTableChange={() => {}} - onOtelEnabledChange={() => {}} - onOtelVersionChange={() => {}} - onTraceIdColumnChange={() => {}} - onSpanIdColumnChange={() => {}} - onOperationNameColumnChange={() => {}} - onParentSpanIdColumnChange={() => {}} - onServiceNameColumnChange={() => {}} - onDurationColumnChange={() => {}} - onDurationUnitChange={() => {}} - onStartTimeColumnChange={() => {}} - onTagsColumnChange={() => {}} - onServiceTagsColumnChange={() => {}} - onEventsColumnPrefixChange={() => {}} - onLinksColumnPrefixChange={() => {}} - onKindColumnChange={() => {}} - onStatusCodeColumnChange={() => {}} - onStatusMessageColumnChange={() => {}} - onInstrumentationLibraryNameColumnChange={() => {}} - onInstrumentationLibraryVersionColumnChange={() => {}} - onStateColumnChange={onStateColumnChange} - /> + <TracesConfig {...defaultTraceConfigProps()} onFlattenNestedChange={onFlattenNestedChange} /> ); expect(result.container.firstChild).not.toBeNull(); - const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label)); + const input = (await result.findAllByRole('checkbox'))[1]; + expect(input).toBeInTheDocument(); + fireEvent.click(input); + expect(onFlattenNestedChange).toHaveBeenCalledTimes(1); + expect(onFlattenNestedChange).toHaveBeenCalledWith(true); + }); + + it('should call onEventsColumnPrefixChange when changed', () => { + const onEventsColumnPrefixChange = jest.fn(); + const result = render( + <TracesConfig {...defaultTraceConfigProps()} onEventsColumnPrefixChange={onEventsColumnPrefixChange} /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.eventsPrefix.label)); expect(input).toBeInTheDocument(); fireEvent.change(input, { target: { value: 'changed' } }); fireEvent.blur(input); - expect(onStateColumnChange).toHaveBeenCalledTimes(1); - expect(onStateColumnChange).toHaveBeenCalledWith('changed'); + expect(onEventsColumnPrefixChange).toHaveBeenCalledTimes(1); + expect(onEventsColumnPrefixChange).toHaveBeenCalledWith('changed'); + }); + + it('should call onLinksColumnPrefixChange when changed', () => { + const onLinksColumnPrefixChange = jest.fn(); + const result = render( + <TracesConfig {...defaultTraceConfigProps()} onLinksColumnPrefixChange={onLinksColumnPrefixChange} /> + ); + expect(result.container.firstChild).not.toBeNull(); + + const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.linksPrefix.label)); + expect(input).toBeInTheDocument(); + fireEvent.change(input, { target: { value: 'changed' } }); + fireEvent.blur(input); + expect(onLinksColumnPrefixChange).toHaveBeenCalledTimes(1); + expect(onLinksColumnPrefixChange).toHaveBeenCalledWith('changed'); }); }); diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx index 404d0b36..f4d05b03 100644 --- a/src/components/configEditor/TracesConfig.tsx +++ b/src/components/configEditor/TracesConfig.tsx @@ -10,8 +10,9 @@ import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { CHTracesConfig } from 'types/config'; import allLabels from 'labels'; import { columnLabelToPlaceholder } from 'data/utils'; +import { Switch } from 'components/queryBuilder/Switch'; -interface TraceConfigProps { +export interface TraceConfigProps { tracesConfig?: CHTracesConfig; onDefaultDatabaseChange: (v: string) => void; onDefaultTableChange: (v: string) => void; @@ -27,14 +28,15 @@ interface TraceConfigProps { onStartTimeColumnChange: (v: string) => void; onTagsColumnChange: (v: string) => void; onServiceTagsColumnChange: (v: string) => void; - onEventsColumnPrefixChange: (v: string) => void; - onLinksColumnPrefixChange: (v: string) => void; onKindColumnChange: (v: string) => void; onStatusCodeColumnChange: (v: string) => void; onStatusMessageColumnChange: (v: string) => void; + onStateColumnChange: (v: string) => void; onInstrumentationLibraryNameColumnChange: (v: string) => void; onInstrumentationLibraryVersionColumnChange: (v: string) => void; - onStateColumnChange: (v: string) => void; + onFlattenNestedChange: (v: boolean) => void; + onEventsColumnPrefixChange: (v: string) => void; + onLinksColumnPrefixChange: (v: string) => void; } export const TracesConfig = (props: TraceConfigProps) => { @@ -43,17 +45,20 @@ export const TracesConfig = (props: TraceConfigProps) => { onOtelEnabledChange, onOtelVersionChange, onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange, onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange, - onTagsColumnChange, onServiceTagsColumnChange, onEventsColumnPrefixChange, onLinksColumnPrefixChange, - onKindColumnChange, onStatusCodeColumnChange, onStatusMessageColumnChange, onInstrumentationLibraryNameColumnChange, - onInstrumentationLibraryVersionColumnChange, onStateColumnChange + onTagsColumnChange, onServiceTagsColumnChange, + onKindColumnChange, onStatusCodeColumnChange, onStatusMessageColumnChange, + onStateColumnChange, + onInstrumentationLibraryNameColumnChange, onInstrumentationLibraryVersionColumnChange, + onFlattenNestedChange, onEventsColumnPrefixChange, onLinksColumnPrefixChange, } = props; let { defaultDatabase, defaultTable, otelEnabled, otelVersion, traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn, - durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn, eventsColumnPrefix, - linksColumnPrefix, kindColumn, statusCodeColumn, statusMessageColumn, instrumentationLibraryNameColumn, - instrumentationLibraryVersionColumn, stateColumn + durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn, + kindColumn, statusCodeColumn, statusMessageColumn, stateColumn, + instrumentationLibraryNameColumn, instrumentationLibraryVersionColumn, + flattenNested, traceEventsColumnPrefix, traceLinksColumnPrefix, } = (props.tracesConfig || {}) as CHTracesConfig; const labels = allLabels.components.Config.TracesConfig; @@ -68,15 +73,16 @@ export const TracesConfig = (props: TraceConfigProps) => { durationColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceDurationTime); tagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceTags); serviceTagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceTags); - eventsColumnPrefix = otelConfig.traceColumnMap.get(ColumnHint.TraceEventsPrefix); - linksColumnPrefix = otelConfig.traceColumnMap.get(ColumnHint.TraceLinksPrefix); kindColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceKind); statusCodeColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusCode); statusMessageColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusMessage); + stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState); instrumentationLibraryNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryName); instrumentationLibraryVersionColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryVersion); - stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState); durationUnit = otelConfig.traceDurationUnit.toString(); + flattenNested = otelConfig.flattenNested; + traceEventsColumnPrefix = otelConfig.traceEventsColumnPrefix; + traceLinksColumnPrefix = otelConfig.traceLinksColumnPrefix; } return ( @@ -201,22 +207,6 @@ export const TracesConfig = (props: TraceConfigProps) => { value={serviceTagsColumn || ''} onChange={onServiceTagsColumnChange} /> - <LabeledInput - disabled={otelEnabled} - label={labels.columns.eventsPrefix.label} - placeholder={columnLabelToPlaceholder(labels.columns.eventsPrefix.label)} - tooltip={labels.columns.eventsPrefix.tooltip} - value={eventsColumnPrefix || ''} - onChange={onEventsColumnPrefixChange} - /> - <LabeledInput - disabled={otelEnabled} - label={labels.columns.linksPrefix.label} - placeholder={columnLabelToPlaceholder(labels.columns.linksPrefix.label)} - tooltip={labels.columns.linksPrefix.tooltip} - value={linksColumnPrefix || ''} - onChange={onLinksColumnPrefixChange} - /> <LabeledInput disabled={otelEnabled} label={labels.columns.kind.label} @@ -241,6 +231,14 @@ export const TracesConfig = (props: TraceConfigProps) => { value={statusMessageColumn || ''} onChange={onStatusMessageColumnChange} /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.state.label} + placeholder={columnLabelToPlaceholder(labels.columns.state.label)} + tooltip={labels.columns.state.tooltip} + value={stateColumn || ''} + onChange={onStateColumnChange} + /> <LabeledInput disabled={otelEnabled} label={labels.columns.instrumentationLibraryName.label} @@ -257,13 +255,29 @@ export const TracesConfig = (props: TraceConfigProps) => { value={instrumentationLibraryVersionColumn || ''} onChange={onInstrumentationLibraryVersionColumnChange} /> + <Switch + disabled={otelEnabled} + label={labels.columns.flattenNested.label} + tooltip={labels.columns.flattenNested.tooltip} + value={flattenNested || false} + onChange={onFlattenNestedChange} + wide + /> <LabeledInput disabled={otelEnabled} - label={labels.columns.state.label} - placeholder={columnLabelToPlaceholder(labels.columns.state.label)} - tooltip={labels.columns.state.tooltip} - value={stateColumn || ''} - onChange={onStateColumnChange} + label={labels.columns.eventsPrefix.label} + placeholder={columnLabelToPlaceholder(labels.columns.eventsPrefix.label)} + tooltip={labels.columns.eventsPrefix.tooltip} + value={traceEventsColumnPrefix || ''} + onChange={onEventsColumnPrefixChange} + /> + <LabeledInput + disabled={otelEnabled} + label={labels.columns.linksPrefix.label} + placeholder={columnLabelToPlaceholder(labels.columns.linksPrefix.label)} + tooltip={labels.columns.linksPrefix.tooltip} + value={traceLinksColumnPrefix || ''} + onChange={onLinksColumnPrefixChange} /> </ConfigSubSection> </ConfigSection> diff --git a/src/components/queryBuilder/Switch.tsx b/src/components/queryBuilder/Switch.tsx index 076b2454..4fca682e 100644 --- a/src/components/queryBuilder/Switch.tsx +++ b/src/components/queryBuilder/Switch.tsx @@ -7,12 +7,13 @@ interface SwitchProps { onChange: (value: boolean) => void; label: string; tooltip: string; + disabled?: boolean; inline?: boolean; wide?: boolean; } export const Switch = (props: SwitchProps) => { - const { value, onChange, label, tooltip, inline, wide } = props; + const { value, onChange, label, tooltip, disabled, inline, wide } = props; const theme = useTheme(); const switchContainerStyle: React.CSSProperties = { @@ -31,6 +32,7 @@ export const Switch = (props: SwitchProps) => { </InlineFormLabel> <div style={switchContainerStyle}> <GrafanaSwitch + disabled={disabled} className="gf-form" value={value} onChange={e => onChange(e.currentTarget.checked)} diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx index ff2318fa..3513fede 100644 --- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx +++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx @@ -5,7 +5,7 @@ import { FiltersEditor } from '../FilterEditor'; import allLabels from 'labels'; import { ModeSwitch } from '../ModeSwitch'; import { getColumnByHint } from 'data/sqlGenerator'; -import { Alert, Collapse, VerticalGroup } from '@grafana/ui'; +import { Alert, Collapse, Stack } from '@grafana/ui'; import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect'; import { Datasource } from 'data/CHDatasource'; import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges'; @@ -18,6 +18,7 @@ import TraceIdInput from '../TraceIdInput'; import { OrderByEditor, getOrderByOptions } from '../OrderByEditor'; import { LimitEditor } from '../LimitEditor'; import { LabeledInput } from 'components/configEditor/LabeledInput'; +import { Switch } from '../Switch'; interface TraceQueryBuilderProps { datasource: Datasource; @@ -39,14 +40,15 @@ interface TraceQueryBuilderState { durationUnit: TimeUnit; tagsColumn?: SelectedColumn; serviceTagsColumn?: SelectedColumn; - eventsColumnPrefix?: SelectedColumn; - linksColumnPrefix?: SelectedColumn; kindColumn?: SelectedColumn; statusCodeColumn?: SelectedColumn; statusMessageColumn?: SelectedColumn; + stateColumn?: SelectedColumn; instrumentationLibraryNameColumn?: SelectedColumn; instrumentationLibraryVersionColumn?: SelectedColumn; - stateColumn?: SelectedColumn; + flattenNested?: boolean; + traceEventsColumnPrefix?: string; + traceLinksColumnPrefix?: string; traceId: string; orderBy: OrderBy[]; limit: number; @@ -61,7 +63,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { const [isColumnsOpen, setColumnsOpen] = useState<boolean>(showConfigWarning); // Toggle Columns collapse section const [isFiltersOpen, setFiltersOpen] = useState<boolean>(!(builderOptions.meta?.isTraceIdMode && builderOptions.meta.traceId)); // Toggle Filters collapse section const labels = allLabels.components.TraceQueryBuilder; - const builderState: TraceQueryBuilderState = useMemo(() => ({ + const builderState = useMemo<TraceQueryBuilderState>(() => ({ isTraceIdMode: builderOptions.meta?.isTraceIdMode || false, otelEnabled: builderOptions.meta?.otelEnabled || false, otelVersion: builderOptions.meta?.otelVersion || '', @@ -75,14 +77,15 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { durationUnit: builderOptions.meta?.traceDurationUnit || TimeUnit.Nanoseconds, tagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceTags), serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags), - eventsColumnPrefix: getColumnByHint(builderOptions, ColumnHint.TraceEventsPrefix), - linksColumnPrefix: getColumnByHint(builderOptions, ColumnHint.TraceLinksPrefix), kindColumn: getColumnByHint(builderOptions, ColumnHint.TraceKind), statusCodeColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusCode), statusMessageColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusMessage), + stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState), instrumentationLibraryNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryName), instrumentationLibraryVersionColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryVersion), - stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState), + flattenNested: Boolean(builderOptions.meta?.flattenNested), + traceEventsColumnPrefix: builderOptions.meta?.traceEventsColumnPrefix || '', + traceLinksColumnPrefix: builderOptions.meta?.traceLinksColumnPrefix || '', traceId: builderOptions.meta?.traceId || '', orderBy: builderOptions.orderBy || [], limit: builderOptions.limit || 0, @@ -100,15 +103,13 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { next.durationTimeColumn, next.tagsColumn, next.serviceTagsColumn, - next.eventsColumnPrefix, - next.linksColumnPrefix, next.serviceTagsColumn, next.kindColumn, next.statusCodeColumn, next.statusMessageColumn, + next.stateColumn, next.instrumentationLibraryNameColumn, next.instrumentationLibraryVersionColumn, - next.stateColumn ].filter(c => c !== undefined) as SelectedColumn[]; builderOptionsDispatch(setOptions({ @@ -120,34 +121,25 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { isTraceIdMode: next.isTraceIdMode, traceDurationUnit: next.durationUnit, traceId: next.traceId, + flattenNested: next.flattenNested, + traceEventsColumnPrefix: next.traceEventsColumnPrefix, + traceLinksColumnPrefix: next.traceLinksColumnPrefix, } })); }, builderState); - // A function to assemble custom column definitions for the prefixed Events/Links columns - const onColumnPrefixChange = (name: keyof TraceQueryBuilderState, columnHint: ColumnHint) => { - const baseOptionChange = onOptionChange(name); - return (name: string) => { - baseOptionChange({ - name: name, - hint: columnHint, - custom: true, - }); - }; - }; - useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch); useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch); useDefaultFilters(builderOptions.table, builderState.isTraceIdMode, isNewQuery, builderOptionsDispatch); const configWarning = showConfigWarning && ( <Alert title="" severity="warning" buttonContent="Close" onRemove={() => setConfigWarningOpen(false)}> - <VerticalGroup> + <Stack> <div> {'To speed up your query building, enter your default trace configuration in your '} <a style={{ textDecoration: 'underline' }} href={`/connections/datasources/edit/${encodeURIComponent(datasource.uid)}#traces-config`}>ClickHouse Data Source settings</a> </div> - </VerticalGroup> + </Stack> </Alert> ); @@ -331,6 +323,20 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { tooltip={labels.columns.statusMessage.tooltip} wide /> + <ColumnSelect + disabled={builderState.otelEnabled} + allColumns={allColumns} + selectedColumn={builderState.stateColumn} + invalid={!builderState.stateColumn} + onColumnChange={onOptionChange('stateColumn')} + columnHint={ColumnHint.TraceState} + label={labels.columns.state.label} + tooltip={labels.columns.state.tooltip} + wide + inline + /> + </div> + <div className="gf-form"> <ColumnSelect disabled={builderState.otelEnabled} allColumns={allColumns} @@ -341,10 +347,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { label={labels.columns.instrumentationLibraryName.label} tooltip={labels.columns.instrumentationLibraryName.tooltip} wide - inline /> - </div> - <div className="gf-form"> <ColumnSelect disabled={builderState.otelEnabled} allColumns={allColumns} @@ -355,18 +358,17 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { label={labels.columns.instrumentationLibraryVersion.label} tooltip={labels.columns.instrumentationLibraryVersion.tooltip} wide + inline /> - <ColumnSelect + </div> + <div className="gf-form"> + <Switch disabled={builderState.otelEnabled} - allColumns={allColumns} - selectedColumn={builderState.stateColumn} - invalid={!builderState.stateColumn} - onColumnChange={onOptionChange('stateColumn')} - columnHint={ColumnHint.TraceState} - label={labels.columns.state.label} - tooltip={labels.columns.state.tooltip} + label={labels.columns.flattenNested.label} + tooltip={labels.columns.flattenNested.tooltip} + value={Boolean(builderState.flattenNested)} + onChange={onOptionChange('flattenNested')} wide - inline /> </div> <div className="gf-form"> @@ -374,8 +376,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { disabled={builderState.otelEnabled} label={labels.columns.eventsPrefix.label} tooltip={labels.columns.eventsPrefix.tooltip} - value={builderState.eventsColumnPrefix?.name || ''} - onChange={onColumnPrefixChange('eventsColumnPrefix', ColumnHint.TraceEventsPrefix)} + value={builderState.traceEventsColumnPrefix || ''} + onChange={onOptionChange('traceEventsColumnPrefix')} /> </div> <div className="gf-form"> @@ -383,8 +385,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => { disabled={builderState.otelEnabled} label={labels.columns.linksPrefix.label} tooltip={labels.columns.linksPrefix.tooltip} - value={builderState.linksColumnPrefix?.name || ''} - onChange={onColumnPrefixChange('linksColumnPrefix', ColumnHint.TraceLinksPrefix)} + value={builderState.traceLinksColumnPrefix || ''} + onChange={onOptionChange('traceLinksColumnPrefix')} /> </div> </Collapse> diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts index 63b7efaf..6b87b57f 100644 --- a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts +++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts @@ -20,7 +20,10 @@ describe('useTraceDefaultsOnMount', () => { meta: { otelEnabled: expect.anything(), otelVersion: undefined, - traceDurationUnit: expect.anything() + traceDurationUnit: expect.anything(), + flattenNested: expect.anything(), + traceEventsColumnPrefix: expect.anything(), + traceLinksColumnPrefix: expect.anything(), } }; @@ -77,7 +80,10 @@ describe('useOtelColumns', () => { const expectedOptions = { columns, meta: { - traceDurationUnit: expect.anything() + traceDurationUnit: expect.anything(), + flattenNested: expect.anything(), + traceEventsColumnPrefix: expect.anything(), + traceLinksColumnPrefix: expect.anything(), } }; diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts index f8747839..fecd3f14 100644 --- a/src/components/queryBuilder/views/traceQueryBuilderHooks.ts +++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts @@ -19,6 +19,9 @@ export const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: bool const defaultDurationUnit = datasource.getDefaultTraceDurationUnit(); const otelVersion = datasource.getTraceOtelVersion(); const defaultColumns = datasource.getDefaultTraceColumns(); + const defaultFlattenNested = datasource.getDefaultTraceFlattenNested(); + const defaultEventsColumnPrefix = datasource.getDefaultTraceEventsColumnPrefix(); + const defaultLinksColumnPrefix = datasource.getDefaultTraceLinksColumnPrefix(); const nextColumns: SelectedColumn[] = []; for (let [hint, colName] of defaultColumns) { @@ -32,7 +35,10 @@ export const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: bool meta: { otelEnabled: Boolean(otelVersion), otelVersion, - traceDurationUnit: defaultDurationUnit + traceDurationUnit: defaultDurationUnit, + flattenNested: defaultFlattenNested, + traceEventsColumnPrefix: defaultEventsColumnPrefix, + traceLinksColumnPrefix: defaultLinksColumnPrefix, } })); didSetDefaults.current = true; @@ -68,7 +74,10 @@ export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builde builderOptionsDispatch(setOptions({ columns, meta: { - traceDurationUnit: otelConfig.traceDurationUnit + traceDurationUnit: otelConfig.traceDurationUnit, + flattenNested: otelConfig.flattenNested, + traceEventsColumnPrefix: otelConfig.traceEventsColumnPrefix, + traceLinksColumnPrefix: otelConfig.traceLinksColumnPrefix, } })); didSetColumns.current = true; diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts index a628ef33..33f58054 100644 --- a/src/data/CHDatasource.ts +++ b/src/data/CHDatasource.ts @@ -493,8 +493,6 @@ export class Datasource traceConfig.startTimeColumn && result.set(ColumnHint.Time, traceConfig.startTimeColumn); traceConfig.tagsColumn && result.set(ColumnHint.TraceTags, traceConfig.tagsColumn); traceConfig.serviceTagsColumn && result.set(ColumnHint.TraceServiceTags, traceConfig.serviceTagsColumn); - traceConfig.eventsColumnPrefix && result.set(ColumnHint.TraceEventsPrefix, traceConfig.eventsColumnPrefix); - traceConfig.linksColumnPrefix && result.set(ColumnHint.TraceLinksPrefix, traceConfig.linksColumnPrefix); traceConfig.kindColumn && result.set(ColumnHint.TraceKind, traceConfig.kindColumn); traceConfig.statusCodeColumn && result.set(ColumnHint.TraceStatusCode, traceConfig.statusCodeColumn); traceConfig.statusMessageColumn && result.set(ColumnHint.TraceStatusMessage, traceConfig.statusMessageColumn); @@ -517,6 +515,18 @@ export class Datasource return this.settings.jsonData.traces?.durationUnit as TimeUnit || TimeUnit.Nanoseconds; } + getDefaultTraceFlattenNested(): boolean { + return this.settings.jsonData.traces?.flattenNested || false; + } + + getDefaultTraceEventsColumnPrefix(): string { + return this.settings.jsonData.traces?.traceEventsColumnPrefix || 'Events'; + } + + getDefaultTraceLinksColumnPrefix(): string { + return this.settings.jsonData.traces?.traceLinksColumnPrefix || 'Links'; + } + async fetchDatabases(): Promise<string[]> { return this.fetchData('SHOW DATABASES'); } diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts index ab809138..d686808c 100644 --- a/src/data/sqlGenerator.test.ts +++ b/src/data/sqlGenerator.test.ts @@ -226,7 +226,7 @@ describe('SQL Generator', () => { expect(sql).toEqual(expectedSqlParts.join(' ')); }); - it('generates trace ID query with additional fields (kind, statusCode, statusMessage, instrumentationLibraryName, instrumentationLibraryVersion, traceState, logs, references) and OTel enabled', () => { + it('generates trace ID query with additional fields, flatten nested disabled', () => { const opts: QueryBuilderOptions = { database: 'default', table: 'otel_traces', @@ -242,8 +242,6 @@ describe('SQL Generator', () => { { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags }, { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags }, { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode }, - { name: 'Events', type: 'Nested(Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String))', hint: ColumnHint.TraceEventsPrefix }, - { name: 'Links', type: 'Nested(TraceId String, SpanId String, TraceState String, Attributes Map(LowCardinality(String), String))', hint: ColumnHint.TraceLinksPrefix }, { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind }, { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage }, { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName }, @@ -257,7 +255,73 @@ describe('SQL Generator', () => { otelVersion: 'latest', traceDurationUnit: TimeUnit.Nanoseconds, isTraceIdMode: true, - traceId: 'abcdefg' + traceId: 'abcdefg', + flattenNested: false, + traceEventsColumnPrefix: 'Events', + traceLinksColumnPrefix: 'Links', + }, + limit: 1000, + orderBy: [] + }; + + const expectedSqlParts = [ + `WITH 'abcdefg' as trace_id, (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_start,`, + `(SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_end`, + 'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,', + '"ServiceName" as serviceName, "SpanName" as operationName, multiply(toUnixTimestamp64Nano("Timestamp"), 0.000001) as startTime,', + 'multiply("Duration", 0.000001) as duration,', + `arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`, + `mapKeys("SpanAttributes")) as tags,`, + `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`, + `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`, + `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))), "Events".Name, "Events".Timestamp, "Events".Attributes) AS logs,`, + `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links".TraceId, "Links".SpanId, "Links".Attributes) AS references,`, + '"Kind" as kind,', + '"StatusMessage" as statusMessage,', + '"InstrumentationLibraryName" as instrumentationLibraryName,', + '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,', + '"TraceState" as traceState', + `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`, + 'LIMIT 1000' + ]; + + const sql = generateSql(opts); + expect(sql).toEqual(expectedSqlParts.join(' ')); + }); + + it('generates trace ID query with additional fields, flatten nested enabled', () => { + const opts: QueryBuilderOptions = { + database: 'default', + table: 'otel_traces', + queryType: QueryType.Traces, + columns: [ + { name: 'TraceId', type: 'String', hint: ColumnHint.TraceId }, + { name: 'SpanId', type: 'String', hint: ColumnHint.TraceSpanId }, + { name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId }, + { name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName }, + { name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName }, + { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time }, + { name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime }, + { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags }, + { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags }, + { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode }, + { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind }, + { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage }, + { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName }, + { name: 'InstrumentationLibraryVersion', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryVersion }, + { name: 'TraceState', type: 'String', hint: ColumnHint.TraceState }, + ], + filters: [], + meta: { + minimized: true, + otelEnabled: true, + otelVersion: 'latest', + traceDurationUnit: TimeUnit.Nanoseconds, + isTraceIdMode: true, + traceId: 'abcdefg', + flattenNested: true, + traceEventsColumnPrefix: 'Events', + traceLinksColumnPrefix: 'Links', }, limit: 1000, orderBy: [] diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts index 84e32be4..6817419d 100644 --- a/src/data/sqlGenerator.ts +++ b/src/data/sqlGenerator.ts @@ -150,18 +150,44 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => { selectParts.push(`if(${escapeIdentifier(traceStatusCode.name)} IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode`); } - const traceEventsPrefix = getColumnByHint(options, ColumnHint.TraceEventsPrefix); - if (traceEventsPrefix !== undefined) { - // It is important to treat the prefixed columns as one nested column, as this - // ensures that the query will work when the column was created with flatten_nested. - selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEventsPrefix.name)}) as logs`); - } - - const traceLinksPrefix = getColumnByHint(options, ColumnHint.TraceLinksPrefix); - if (traceLinksPrefix !== undefined) { - // It is important to treat the prefixed columns as one nested column, as this - // ensures that the query will work when the column was created with flatten_nested. - selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}) AS references`); + const flattenNested = Boolean(options.meta?.flattenNested); + + const traceEventsPrefix = options.meta?.traceEventsColumnPrefix || ''; + if (traceEventsPrefix !== '') { + if (flattenNested) { + selectParts.push([ + `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000),`, + `arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]),`, + `mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))),`, + `${escapeIdentifier(traceEventsPrefix)}) as logs` + ].join(' ')); + } else { + selectParts.push([ + `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)),`, + `arrayMap( key -> map('key', key, 'value', attributes[key]),`, + `mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),`, + `${escapeIdentifier(traceEventsPrefix)}.Name, ${escapeIdentifier(traceEventsPrefix)}.Timestamp,`, + `${escapeIdentifier(traceEventsPrefix)}.Attributes) AS logs` + ].join(' ')); + } + } + + const traceLinksPrefix = options.meta?.traceLinksColumnPrefix || ''; + if (traceLinksPrefix !== '') { + if (flattenNested) { + selectParts.push([ + `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]),`, + `mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))),`, + `${escapeIdentifier(traceLinksPrefix)}) AS references` + ].join(' ')); + } else { + selectParts.push([ + `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]),`, + `mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))),`, + `${escapeIdentifier(traceLinksPrefix)}.TraceId, ${escapeIdentifier(traceLinksPrefix)}.SpanId,`, + `${escapeIdentifier(traceLinksPrefix)}.Attributes) AS references` + ].join(' ')); + } } const traceKind = getColumnByHint(options, ColumnHint.TraceKind); diff --git a/src/labels.ts b/src/labels.ts index 3ac184ae..c557556c 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -204,13 +204,17 @@ export default { label: 'Service Tags column', tooltip: 'Column for the service tags' }, + flattenNested: { + label: 'Use Flatten Nested', + tooltip: 'Enable if your traces table was created with flatten_nested=1', + }, eventsPrefix: { - label: 'Events column', - tooltip: 'Prefix for the events column' + label: 'Events prefix', + tooltip: 'Prefix for the events column (Events.Timestamp, Events.Name, etc.)' }, linksPrefix: { - label: 'Links column', - tooltip: 'Prefix for the trace references column' + label: 'Links prefix', + tooltip: 'Prefix for the trace references column (Links.TraceId, Links.TraceState, etc.)' }, kind: { label: 'Kind column', @@ -453,6 +457,10 @@ export default { label: 'Service Tags Column', tooltip: 'Column that contains the service tags' }, + flattenNested: { + label: 'Use Flatten Nested', + tooltip: 'Enable if your traces table was created with flatten_nested=1', + }, eventsPrefix: { label: 'Events Prefix', tooltip: 'Prefix for the events column' @@ -475,11 +483,11 @@ export default { }, instrumentationLibraryName: { label: 'Library Name Column', - tooltip: 'Column that contains the instrumentation library name' + tooltip: 'Column that contains the instrumentation library name (Optional)' }, instrumentationLibraryVersion: { label: 'Library Version Column', - tooltip: 'Column that contains the instrumentation library version' + tooltip: 'Column that contains the instrumentation library version (Optional)' }, state: { label: 'State Column', @@ -519,8 +527,6 @@ export default { [ColumnHint.TraceTags]: 'Tags', [ColumnHint.TraceServiceTags]: 'Service Tags', [ColumnHint.TraceStatusCode]: 'Status Code', - [ColumnHint.TraceEventsPrefix]: 'Events Prefix', - [ColumnHint.TraceLinksPrefix]: 'Links Prefix', [ColumnHint.TraceKind]: 'Kind', [ColumnHint.TraceStatusMessage]: 'Status Message', [ColumnHint.TraceInstrumentationLibraryName]: 'Instrumentation Library Name', diff --git a/src/otel.ts b/src/otel.ts index 24171ef0..dc0aa8ae 100644 --- a/src/otel.ts +++ b/src/otel.ts @@ -15,6 +15,9 @@ export interface OtelVersion { traceTable: string; traceColumnMap: Map<ColumnHint, string>; traceDurationUnit: TimeUnit.Nanoseconds; + flattenNested: boolean; + traceEventsColumnPrefix: string; + traceLinksColumnPrefix: string; } const otel129: OtelVersion = { @@ -49,15 +52,14 @@ const otel129: OtelVersion = { [ColumnHint.TraceTags, 'SpanAttributes'], [ColumnHint.TraceServiceTags, 'ResourceAttributes'], [ColumnHint.TraceStatusCode, 'StatusCode'], - [ColumnHint.TraceEventsPrefix, 'Events'], - [ColumnHint.TraceLinksPrefix, 'Links'], [ColumnHint.TraceKind, 'SpanKind'], [ColumnHint.TraceStatusMessage, 'StatusMessage'], - [ColumnHint.TraceInstrumentationLibraryName, 'InstrumentationLibraryName'], - [ColumnHint.TraceInstrumentationLibraryVersion, 'InstrumentationLibraryVersion'], - [ColumnHint.TraceState, 'State'], + [ColumnHint.TraceState, 'TraceState'], ]), + flattenNested: false, traceDurationUnit: TimeUnit.Nanoseconds, + traceEventsColumnPrefix: 'Events', + traceLinksColumnPrefix: 'Links', }; export const versions: readonly OtelVersion[] = [ diff --git a/src/types/config.ts b/src/types/config.ts index ab8255d7..a8e19972 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -92,14 +92,16 @@ export interface CHTracesConfig { startTimeColumn?: string; tagsColumn?: string; serviceTagsColumn?: string; - eventsColumnPrefix?: string; - linksColumnPrefix?: string; kindColumn?: string; statusCodeColumn?: string; statusMessageColumn?: string; + stateColumn?: string; instrumentationLibraryNameColumn?: string; instrumentationLibraryVersionColumn?: string; - stateColumn?: string; + + flattenNested?: boolean; + traceEventsColumnPrefix?: string; + traceLinksColumnPrefix?: string; } export interface AliasTableEntry { diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts index f307446f..2e6a94b1 100644 --- a/src/types/queryBuilder.ts +++ b/src/types/queryBuilder.ts @@ -56,6 +56,14 @@ export interface QueryBuilderOptions { isTraceIdMode?: boolean; traceId?: string; + /** + * True if "Nested" column types should be treated as if they + * were created with flatten_nested=1. Applies to trace Events and Links columns. + */ + flattenNested?: boolean; + traceEventsColumnPrefix?: string; + traceLinksColumnPrefix?: string; + // Logs & Traces otelEnabled?: boolean; otelVersion?: string; @@ -133,8 +141,6 @@ export enum ColumnHint { TraceTags = 'trace_tags', TraceServiceTags = 'trace_service_tags', TraceStatusCode = 'trace_status_code', - TraceEventsPrefix = 'trace_events_prefix', - TraceLinksPrefix = 'trace_links_prefix', TraceKind = 'trace_kind', TraceStatusMessage = 'trace_status_message', TraceInstrumentationLibraryName = 'instrumentation_library_name', diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx index 72f88fd5..babc82df 100644 --- a/src/views/CHConfigEditor.tsx +++ b/src/views/CHConfigEditor.tsx @@ -4,7 +4,7 @@ import { onUpdateDatasourceJsonDataOption, onUpdateDatasourceSecureJsonDataOption, } from '@grafana/data'; -import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, HorizontalGroup, Alert, VerticalGroup } from '@grafana/ui'; +import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, Alert, Stack } from '@grafana/ui'; import { CertificationKey } from '../components/ui/CertificationKey'; import { CHConfig, @@ -191,7 +191,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { const uidWarning = (!options.uid) && ( <Alert title="" severity="warning" buttonContent="Close"> - <VerticalGroup> + <Stack> <div> {'This datasource is missing the'} <code>uid</code> @@ -204,7 +204,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { >provisioned via YAML</a> {', please verify the UID is set. This is required to enable data linking between logs and traces.'} </div> - </VerticalGroup> + </Stack> </Alert> ); @@ -450,14 +450,15 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { onStartTimeColumnChange={c => onTracesConfigChange('startTimeColumn', c)} onTagsColumnChange={c => onTracesConfigChange('tagsColumn', c)} onServiceTagsColumnChange={c => onTracesConfigChange('serviceTagsColumn', c)} - onEventsColumnPrefixChange={c => onTracesConfigChange('eventsColumnPrefix', c)} - onLinksPrefixColumnChange={c => onTracesConfigChange('linksColumnPrefix', c)} onKindColumnChange={c => onTracesConfigChange('kindColumn', c)} onStatusCodeColumnChange={c => onTracesConfigChange('statusCodeColumn', c)} onStatusMessageColumnChange={c => onTracesConfigChange('statusMessageColumn', c)} + onStateColumnChange={c => onTracesConfigChange('stateColumn', c)} onInstrumentationLibraryNameColumnChange={c => onTracesConfigChange('instrumentationLibraryNameColumn', c)} onInstrumentationLibraryVersionColumnChange={c => onTracesConfigChange('instrumentationLibraryVersionColumn', c)} - onStateColumnChange={c => onTracesConfigChange('stateColumn', c)} + onFlattenNestedChange={c => onTracesConfigChange('flattenNested', c)} + onEventsColumnPrefixChange={c => onTracesConfigChange('traceEventsColumnPrefix', c)} + onLinksColumnPrefixChange={c => onTracesConfigChange('traceLinksColumnPrefix', c)} /> <Divider /> @@ -478,7 +479,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { <ConfigSubSection title="Custom Settings"> {customSettings.map(({ setting, value }, i) => { return ( - <HorizontalGroup key={i}> + <Stack key={i} direction='row'> <Field label={`Setting`} aria-label={`Setting`}> <Input value={setting} @@ -507,7 +508,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { }} ></Input> </Field> - </HorizontalGroup> + </Stack> ); })} <Button