Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SpencerTorres committed Sep 30, 2024
1 parent ada2cb7 commit 18b3c8d
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 15 deletions.
57 changes: 57 additions & 0 deletions src/components/LogContextPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { render } from '@testing-library/react';
import LogsContextPanel, { _testExports } from './LogsContextPanel';
import { Components } from 'selectors';

describe('LogsContextPanel', () => {
it('shows an alert when no columns are matched', () => {
const result = render(<LogsContextPanel columns={[]} datasourceUid="test-uid" />);
expect(result.getByTestId(Components.LogsContextPanel.alert)).toBeInTheDocument();
});

it('renders LogContextKey components for each column', () => {
const mockColumns = [
{ name: 'host', value: '127.0.0.1' },
{ name: 'service', value: 'test-api' },
];

const result = render(<LogsContextPanel columns={mockColumns} datasourceUid="test-uid" />);

expect(result.getAllByTestId(Components.LogsContextPanel.LogContextKey.icon)).toHaveLength(2);
expect(result.getByText('host')).toBeInTheDocument();
expect(result.getByText('127.0.0.1')).toBeInTheDocument();
expect(result.getByText('service')).toBeInTheDocument();
expect(result.getByText('test-api')).toBeInTheDocument();
});
});

describe('LogContextKey', () => {
const LogContextKey = _testExports.LogContextKey;

it('renders the expected keys', () => {
const props = {
name: 'testName',
value: 'testValue',
primaryColor: '#000',
primaryTextColor: '#aaa',
secondaryColor: '#111',
secondaryTextColor: '#bbb',
};


const result = render(<LogContextKey {...props} />);

expect(result.getByTestId(Components.LogsContextPanel.LogContextKey.icon)).toBeInTheDocument();
expect(result.getByText('testName')).toBeInTheDocument();
expect(result.getByText('testValue')).toBeInTheDocument();
});
});

describe('iconMatcher', () => {
const iconMatcher = _testExports.iconMatcher;

it('returns correct icons for different context names', () => {
expect(iconMatcher('database')).toBe('database');
expect(iconMatcher('???')).toBe('align-left');
});
});
10 changes: 8 additions & 2 deletions src/components/LogsContextPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { Alert, Icon, IconName, useTheme2, VerticalGroup } from '@grafana/ui';
import { css } from '@emotion/css';
import { LogContextColumn } from 'data/CHDatasource';
import { Components } from 'selectors';


const LogsContextPanelStyles = css`
Expand All @@ -22,7 +23,7 @@ const LogsContextPanel = (props: LogContextPanelProps) => {

if (!columns || columns.length === 0) {
return (
<Alert title="" severity="warning">
<Alert data-testid={Components.LogsContextPanel.alert} title="" severity="warning">
<VerticalGroup>
<div>
{'Unable to match any context columns. Make sure your query returns at least one log context column from your '}
Expand Down Expand Up @@ -144,7 +145,7 @@ const LogContextKey = (props: LogContextKeyProps) => {
return (
<div className={styles.container}>
<div className={styles.containerLeft}>
<Icon name={iconMatcher(name)} size="md" />
<Icon data-testid={Components.LogsContextPanel.LogContextKey.icon} name={iconMatcher(name)} size="md" />
<span className={styles.contextName}>{name}</span>
</div>
<span className={styles.contextValue}>{value}</span>
Expand All @@ -153,3 +154,8 @@ const LogContextKey = (props: LogContextKeyProps) => {
}

export default LogsContextPanel;

export const _testExports = {
iconMatcher,
LogContextKey,
};
20 changes: 19 additions & 1 deletion src/components/configEditor/LogsConfig.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -34,6 +36,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -58,6 +62,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -82,11 +88,15 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();

const input = result.getByRole('checkbox');
const checkboxes = result.getAllByRole('checkbox');
expect(checkboxes).toHaveLength(2);
const input = checkboxes[0];
expect(input).toBeInTheDocument();
fireEvent.click(input);
expect(onOtelEnabledChange).toHaveBeenCalledTimes(1);
Expand All @@ -105,6 +115,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -129,6 +141,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={onTimeColumnChange}
onLevelColumnChange={() => {}}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -153,6 +167,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={onLevelColumnChange}
onMessageColumnChange={() => {}}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand All @@ -177,6 +193,8 @@ describe('LogsConfig', () => {
onTimeColumnChange={() => {}}
onLevelColumnChange={() => {}}
onMessageColumnChange={onMessageColumnChange}
onSelectContextColumnsChange={() => {}}
onContextColumnsChange={() => {}}
/>
);
expect(result.container.firstChild).not.toBeNull();
Expand Down
67 changes: 62 additions & 5 deletions src/components/queryBuilder/views/logsQueryBuilderHooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import otel from 'otel';
describe('useLogDefaultsOnMount', () => {
it('should call builderOptionsDispatch with default log columns', async () => {
const builderOptionsDispatch = jest.fn();
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
// Should not be included, since shouldSelectLogContextColumns returns false
jest.spyOn(mockDatasource, 'getLogContextColumnNames').mockReturnValue(['SampleColumn']);
jest.spyOn(mockDatasource, 'getLogsOtelVersion').mockReturnValue(undefined);
jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map<ColumnHint, string>([[ColumnHint.Time, 'timestamp']]));

Expand All @@ -27,6 +30,33 @@ describe('useLogDefaultsOnMount', () => {
expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions)));
});

it('should call builderOptionsDispatch with default log columns, including log context columns', async () => {
const builderOptionsDispatch = jest.fn();
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(true);
// timestamp is included, but also provided as a Log Context column. It should only appear once.
jest.spyOn(mockDatasource, 'getLogContextColumnNames').mockReturnValue(['timestamp', 'SampleColumn']);
jest.spyOn(mockDatasource, 'getLogsOtelVersion').mockReturnValue(undefined);
jest.spyOn(mockDatasource, 'getDefaultLogsColumns').mockReturnValue(new Map<ColumnHint, string>([[ColumnHint.Time, 'timestamp']]));

renderHook(() => useLogDefaultsOnMount(mockDatasource, true, {} as QueryBuilderOptions, builderOptionsDispatch));

const expectedOptions = {
database: expect.anything(),
table: expect.anything(),
columns: [
{ name: 'timestamp', hint: ColumnHint.Time },
{ name: 'SampleColumn' }
],
meta: {
otelEnabled: expect.anything(),
otelVersion: undefined
}
};

expect(builderOptionsDispatch).toHaveBeenCalledTimes(1);
expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions)));
});

it('should not call builderOptionsDispatch after defaults are set', async () => {
const builderOptions = {} as QueryBuilderOptions;
const builderOptionsDispatch = jest.fn();
Expand All @@ -50,24 +80,30 @@ describe('useOtelColumns', () => {
const testOtelVersion = otel.getLatestVersion();

it('should not call builderOptionsDispatch if OTEL is already enabled', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();
renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch));

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

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

it('should not call builderOptionsDispatch if OTEL is disabled', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
// Should not be included, since shouldSelectLogContextColumns returns false
jest.spyOn(mockDatasource, 'getLogContextColumnNames').mockReturnValue(['SampleColumn']);
const builderOptionsDispatch = jest.fn();
renderHook(() => useOtelColumns(true, testOtelVersion.version, builderOptionsDispatch));
renderHook(() => useOtelColumns(mockDatasource, true, testOtelVersion.version, builderOptionsDispatch));

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();

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

Expand All @@ -79,11 +115,32 @@ describe('useOtelColumns', () => {
expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions)));
});

it('should call builderOptionsDispatch with log context columns when auto-select is enabled', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(true);
// 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();

let otelEnabled = false;
const hook = renderHook(enabled => useOtelColumns(mockDatasource, 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' });
const expectedOptions = { columns };

expect(builderOptionsDispatch).toHaveBeenCalledTimes(1);
expect(builderOptionsDispatch).toHaveBeenCalledWith(expect.objectContaining(setOptions(expectedOptions)));
});

it('should not call builderOptionsDispatch after OTEL columns are set', async () => {
jest.spyOn(mockDatasource, 'shouldSelectLogContextColumns').mockReturnValue(false);
const builderOptionsDispatch = jest.fn();

let otelEnabled = false; // OTEL is off
const hook = renderHook(enabled => useOtelColumns(enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled });
const hook = renderHook(enabled => useOtelColumns(mockDatasource, enabled, testOtelVersion.version, builderOptionsDispatch), { initialProps: otelEnabled });
otelEnabled = true;
hook.rerender(otelEnabled); // OTEL is on, columns are set
hook.rerender(otelEnabled); // OTEL still on, should not set again
Expand Down Expand Up @@ -134,7 +191,7 @@ describe('useDefaultTimeColumn', () => {
const timeColumn = undefined;
const otelEnabled = false;

const hook = renderHook(table =>
const hook = renderHook(table =>
useDefaultTimeColumn(
mockDatasource,
allColumns,
Expand Down
12 changes: 6 additions & 6 deletions src/components/queryBuilder/views/logsQueryBuilderHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export const useLogDefaultsOnMount = (datasource: Datasource, isNewQuery: boolea
const defaultTable = datasource.getDefaultLogsTable() || datasource.getDefaultTable();
const otelVersion = datasource.getLogsOtelVersion();
const defaultColumns = datasource.getDefaultLogsColumns();
const shouldSelectLogContextColumns = datasource.shouldSelectLogContextColumns();
const contextColumnNames = datasource.getLogContextColumnNames();

const nextColumns: SelectedColumn[] = [];
const includedColumns = new Set<string>();
Expand All @@ -29,7 +27,9 @@ export const useLogDefaultsOnMount = (datasource: Datasource, isNewQuery: boolea
includedColumns.add(colName);
}

if (shouldSelectLogContextColumns) {
if (datasource.shouldSelectLogContextColumns()) {
const contextColumnNames = datasource.getLogContextColumnNames();

for (let columnName of contextColumnNames) {
if (includedColumns.has(columnName)) {
continue;
Expand Down Expand Up @@ -81,9 +81,9 @@ export const useOtelColumns = (datasource: Datasource, otelEnabled: boolean, ote
includedColumns.add(name);
});

const shouldSelectLogContextColumns = datasource.shouldSelectLogContextColumns();
const contextColumnNames = datasource.getLogContextColumnNames();
if (shouldSelectLogContextColumns) {
if (datasource.shouldSelectLogContextColumns()) {
const contextColumnNames = datasource.getLogContextColumnNames();

for (let columnName of contextColumnNames) {
if (includedColumns.has(columnName)) {
continue;
Expand Down
6 changes: 6 additions & 0 deletions src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ export const Components = {
aliasTableInput: 'config__alias-table-config__alias-table-input',
}
},
LogsContextPanel: {
alert: 'logs-context-panel__alert',
LogContextKey: {
icon: 'logs-context-panel__logs-context-key__icon',
}
},
QueryBuilder: {
expandBuilderButton: 'query-builder__expand-builder-button',
LogsQueryBuilder: {
Expand Down
4 changes: 3 additions & 1 deletion src/views/CHConfigEditorHooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ describe('useConfigDefaults', () => {
version: pluginVersion,
protocol: Protocol.Native,
logs: {
defaultTable: defaultLogsTable
defaultTable: defaultLogsTable,
selectContextColumns: true,
contextColumns: []
},
traces: {
defaultTable: defaultTraceTable
Expand Down

0 comments on commit 18b3c8d

Please sign in to comment.