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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/queryBuilder/views/LogsQueryBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const LogsQueryBuilder = (props: LogsQueryBuilderProps) => {
}, builderState);

useLogDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch);
useOtelColumns(datasource, builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch);
useOtelColumns(datasource, allColumns, builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch);
useDefaultTimeColumn(
datasource,
allColumns,
Expand Down
42 changes: 34 additions & 8 deletions src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ describe('useOtelColumns', () => {
it('should not call builderOptionsDispatch if OTEL is already enabled', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();
const allColumns: readonly TableColumn[] = [{ name: 'LogAttributes', type: 'Map(String, String)', picklistValues: [] }];

renderHook(() => useOtelColumns(mockDatasource, true, testOtelVersion.version, builderOptionsDispatch));
renderHook(() => useOtelColumns(mockDatasource, allColumns, true, testOtelVersion.version, builderOptionsDispatch));

expect(builderOptionsDispatch).toHaveBeenCalledTimes(0);
});
Expand All @@ -101,25 +102,45 @@ describe('useOtelColumns', () => {
// Should not be included, since shouldSelectLogContextColumns returns false
jest.spyOn(mockDatasource, 'getLogContextColumnNames').mockReturnValue(['SampleColumn']);
const builderOptionsDispatch = jest.fn();
renderHook(() => useOtelColumns(mockDatasource, true, testOtelVersion.version, builderOptionsDispatch));
const allColumns: readonly TableColumn[] = [{ name: 'LogAttributes', type: 'Map(String, String)', picklistValues: [] }];

renderHook(() => useOtelColumns(mockDatasource, allColumns, true, testOtelVersion.version, builderOptionsDispatch));

expect(builderOptionsDispatch).toHaveBeenCalledTimes(0);
});

it('should not call builderOptionsDispatch if allColumns is empty', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();

let otelEnabled = false;
const hook = renderHook(
(enabled) => useOtelColumns(mockDatasource, [], enabled, testOtelVersion.version, builderOptionsDispatch),
{ initialProps: otelEnabled }
);
otelEnabled = true;
hook.rerender(otelEnabled);

expect(builderOptionsDispatch).toHaveBeenCalledTimes(0);
});

it('should call builderOptionsDispatch with columns when OTEL is toggled on', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();
const allColumns: readonly TableColumn[] = [{ name: 'LogAttributes', type: 'Map(String, String)', picklistValues: [] }];

let otelEnabled = false;
const hook = renderHook(
(enabled) => useOtelColumns(mockDatasource, enabled, testOtelVersion.version, builderOptionsDispatch),
(enabled) => useOtelColumns(mockDatasource, allColumns, enabled, testOtelVersion.version, builderOptionsDispatch),
{ initialProps: otelEnabled }
);
otelEnabled = true;
hook.rerender(otelEnabled);

const columns: SelectedColumn[] = [];
testOtelVersion.logColumnMap.forEach((v, k) => columns.push({ name: v, hint: k }));
testOtelVersion.logColumnMap.forEach((v, k) => {
columns.push({ name: v, hint: k, type: allColumns.find((c) => c.name === v)?.type });
});
const expectedOptions = { columns };

expect(builderOptionsDispatch).toHaveBeenCalledTimes(1);
Expand All @@ -131,18 +152,22 @@ describe('useOtelColumns', () => {
// Timestamp is an OTel column, but also provided as a Log Context column. It should only appear once.
jest.spyOn(mockDatasource, 'getLogContextColumnNames').mockReturnValue(['Timestamp', 'SampleColumn']);
const builderOptionsDispatch = jest.fn();
const allColumns: readonly TableColumn[] = [
{ name: 'LogAttributes', type: 'Map(String, String)', picklistValues: [] },
{ name: 'SampleColumn', type: 'String', picklistValues: [] },
];

let otelEnabled = false;
const hook = renderHook(
(enabled) => useOtelColumns(mockDatasource, enabled, testOtelVersion.version, builderOptionsDispatch),
(enabled) => useOtelColumns(mockDatasource, allColumns, enabled, testOtelVersion.version, builderOptionsDispatch),
{ initialProps: otelEnabled }
);
otelEnabled = true;
hook.rerender(otelEnabled);

const columns: SelectedColumn[] = [];
testOtelVersion.logColumnMap.forEach((v, k) => columns.push({ name: v, hint: k }));
columns.push({ name: 'SampleColumn' });
testOtelVersion.logColumnMap.forEach((v, k) => {columns.push({ name: v, hint: k, type: allColumns.find((c) => c.name === v)?.type })});
columns.push({ name: 'SampleColumn', type: allColumns.find((c) => c.name === 'SampleColumn')?.type });
const expectedOptions = { columns };

expect(builderOptionsDispatch).toHaveBeenCalledTimes(1);
Expand All @@ -152,10 +177,11 @@ describe('useOtelColumns', () => {
it('should not call builderOptionsDispatch after OTEL columns are set', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();
const allColumns: readonly TableColumn[] = [{ name: 'LogAttributes', type: 'Map(String, String)', picklistValues: [] }];

let otelEnabled = false; // OTEL is off
const hook = renderHook(
(enabled) => useOtelColumns(mockDatasource, enabled, testOtelVersion.version, builderOptionsDispatch),
(enabled) => useOtelColumns(mockDatasource, allColumns, enabled, testOtelVersion.version, builderOptionsDispatch),
{ initialProps: otelEnabled }
);
otelEnabled = true;
Expand Down
9 changes: 5 additions & 4 deletions src/components/queryBuilder/views/logsQueryBuilderHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const useLogDefaultsOnMount = (
*/
export const useOtelColumns = (
datasource: Datasource,
allColumns: readonly TableColumn[],
otelEnabled: boolean,
otelVersion: string,
builderOptionsDispatch: React.Dispatch<BuilderOptionsReducerAction>
Expand All @@ -94,7 +95,7 @@ export const useOtelColumns = (
}

useEffect(() => {
if (!otelEnabled || didSetColumns.current) {
if (!otelEnabled || didSetColumns.current || allColumns.length === 0) {
return;
}

Expand All @@ -107,7 +108,7 @@ export const useOtelColumns = (
const columns: SelectedColumn[] = [];
const includedColumns = new Set<string>();
logColumnMap.forEach((name, hint) => {
columns.push({ name, hint });
columns.push({ name, hint, type: allColumns.find((c) => c.name === name)?.type });
includedColumns.add(name);
});

Expand All @@ -119,14 +120,14 @@ export const useOtelColumns = (
continue;
}

columns.push({ name: columnName });
columns.push({ name: columnName, type: allColumns.find((c) => c.name === columnName)?.type });
includedColumns.add(columnName);
}
}

builderOptionsDispatch(setOptions({ columns }));
didSetColumns.current = true;
}, [datasource, otelEnabled, otelVersion, builderOptionsDispatch]);
}, [datasource, allColumns, otelEnabled, otelVersion, builderOptionsDispatch]);
};

// Finds and selects a default log time column, updates when table changes
Expand Down
35 changes: 35 additions & 0 deletions src/data/CHDatasource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,4 +674,39 @@ describe('ClickHouseDatasource', () => {
});
});
});

describe('modifyQuery', () => {
const query: CHBuilderQuery = {
pluginVersion: '',
refId: 'A',
editorType: EditorType.Builder,
rawSql: '',
builderOptions: {
database: 'default',
table: 'logs',
queryType: QueryType.Logs,
mode: BuilderMode.List,
columns: [{ name: 'LogAttributes', hint: ColumnHint.LogLabels, type: 'Map(String, String)' }],
},
};

let datasource: Datasource;
beforeEach(() => {
datasource = cloneDeep(mockDatasource);
});

it('should set mapKey to columnName for Map type LogLabels', () => {
const frame = {
fields: [{ name: 'labels', values: { get: () => ({ service_name: 'value' }), length: 1 } }],
} as any;

const result = datasource.modifyQuery(query, {
type: 'ADD_FILTER',
options: { key: 'service_name', value: 'my-service' },
frame,
} as any);

expect((result as CHBuilderQuery).builderOptions.filters![0].mapKey).toBe('service_name');
});
});
});
2 changes: 1 addition & 1 deletion src/data/CHDatasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export class Datasource
getColumnByHint(query.builderOptions, ColumnHint.LogLabels);
const column = lookupByAlias || lookupByName || lookupByLogsAlias || lookupByLogLabels;
const columnType = column ? column.type || '' : '';
const hasMapKey = mapKey !== '' || Boolean(lookupByLogLabels);
const hasMapKey = (mapKey ||= lookupByLogLabels ? columnName : '') !== '';

let nextFilters: Filter[] = query.builderOptions.filters?.slice() || [];
if (action.type === 'ADD_FILTER') {
Expand Down