264 lines
9.1 KiB
TypeScript
264 lines
9.1 KiB
TypeScript
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { useState } from 'react';
|
|
|
|
import { config } from '@grafana/runtime';
|
|
|
|
import { TraceqlSearchScope } from '../dataquery.gen';
|
|
import { TempoDatasource } from '../datasource';
|
|
import TempoLanguageProvider from '../language_provider';
|
|
import { initTemplateSrv } from '../test/test_utils';
|
|
import { TempoQuery } from '../types';
|
|
|
|
import TraceQLSearch from './TraceQLSearch';
|
|
|
|
const getOptionsV2 = jest.fn().mockImplementation(() => {
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve([
|
|
{
|
|
value: 'customer',
|
|
label: 'customer',
|
|
type: 'string',
|
|
},
|
|
{
|
|
value: 'driver',
|
|
label: 'driver',
|
|
type: 'string',
|
|
},
|
|
]);
|
|
}, 1000);
|
|
});
|
|
});
|
|
|
|
const getTags = jest.fn().mockImplementation(() => {
|
|
return ['foo', 'bar'];
|
|
});
|
|
|
|
jest.mock('../language_provider', () => {
|
|
return jest.fn().mockImplementation(() => {
|
|
return { getOptionsV2, getTags };
|
|
});
|
|
});
|
|
|
|
describe('TraceQLSearch', () => {
|
|
const expectedValues = {
|
|
interpolationVar: 'interpolationText',
|
|
interpolationText: 'interpolationText',
|
|
interpolationVarWithPipe: 'interpolationTextOne|interpolationTextTwo',
|
|
scopedInterpolationText: 'scopedInterpolationText',
|
|
};
|
|
initTemplateSrv([{ name: 'templateVariable1' }, { name: 'templateVariable2' }], expectedValues);
|
|
|
|
let user: ReturnType<typeof userEvent.setup>;
|
|
|
|
const datasource: TempoDatasource = {
|
|
search: {
|
|
filters: [
|
|
{
|
|
id: 'service-name',
|
|
tag: 'service.name',
|
|
operator: '=',
|
|
scope: TraceqlSearchScope.Resource,
|
|
},
|
|
],
|
|
},
|
|
} as TempoDatasource;
|
|
datasource.isStreamingSearchEnabled = () => false;
|
|
datasource.isStreamingMetricsEnabled = () => false;
|
|
const lp = new TempoLanguageProvider(datasource);
|
|
lp.getIntrinsics = () => ['duration'];
|
|
lp.generateQueryFromFilters = () => '{}';
|
|
datasource.languageProvider = lp;
|
|
let query: TempoQuery = {
|
|
refId: 'A',
|
|
queryType: 'traceqlSearch',
|
|
key: 'Q-595a9bbc-2a25-49a7-9249-a52a0a475d83-0',
|
|
query: '',
|
|
filters: [{ id: 'min-duration', operator: '>', valueType: 'duration', tag: 'duration' }],
|
|
};
|
|
const onChange = (q: TempoQuery) => {
|
|
query = q;
|
|
};
|
|
const onClearResults = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
// Need to use delay: null here to work with fakeTimers
|
|
// see https://github.com/testing-library/user-event/issues/833
|
|
user = userEvent.setup({ delay: null });
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it('should only show add/remove tag when necessary', async () => {
|
|
const TraceQLSearchWithProps = () => {
|
|
const [query, setQuery] = useState<TempoQuery>({
|
|
refId: 'A',
|
|
queryType: 'traceqlSearch',
|
|
key: 'Q-595a9bbc-2a25-49a7-9249-a52a0a475d83-0',
|
|
filters: [],
|
|
});
|
|
return (
|
|
<TraceQLSearch
|
|
datasource={datasource}
|
|
query={query}
|
|
onChange={(q: TempoQuery) => setQuery(q)}
|
|
onClearResults={onClearResults}
|
|
/>
|
|
);
|
|
};
|
|
render(<TraceQLSearchWithProps />);
|
|
|
|
await act(async () => {
|
|
expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one
|
|
expect(screen.queryAllByLabelText('Remove tag').length).toBe(0); // mot filled in the default tag, so no values to remove
|
|
expect(screen.getAllByText('Select tag').length).toBe(1);
|
|
});
|
|
|
|
await user.click(screen.getByText('Select tag'));
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
await user.click(screen.getByText('foo'));
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
await user.click(screen.getAllByText('Select value')[2]);
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
await user.click(screen.getByText('driver'));
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
await act(async () => {
|
|
expect(screen.getAllByLabelText('Add tag').length).toBe(1);
|
|
expect(screen.getAllByLabelText(/Remove tag/).length).toBe(1);
|
|
});
|
|
|
|
await user.click(screen.getByLabelText('Add tag'));
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the new tag, so no need to add another one
|
|
expect(screen.getAllByLabelText(/Remove tag/).length).toBe(2); // one for each tag
|
|
|
|
await user.click(screen.getAllByLabelText(/Remove tag/)[1]);
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
expect(screen.queryAllByLabelText('Add tag').length).toBe(1); // filled in the default tag, so can add another one
|
|
expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(1); // filled in the default tag, so can remove values
|
|
|
|
await user.click(screen.getAllByLabelText(/Remove tag/)[0]);
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one
|
|
expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(0); // mot filled in the default tag, so no values to remove
|
|
});
|
|
});
|
|
|
|
it('should update operator when new value is selected in operator input', async () => {
|
|
const { container } = render(
|
|
<TraceQLSearch datasource={datasource} query={query} onChange={onChange} onClearResults={onClearResults} />
|
|
);
|
|
|
|
const minDurationOperator = container.querySelector(`input[aria-label="select min-duration operator"]`);
|
|
expect(minDurationOperator).not.toBeNull();
|
|
expect(minDurationOperator).toBeInTheDocument();
|
|
|
|
if (minDurationOperator) {
|
|
await user.click(minDurationOperator);
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
const regexOp = await screen.findByText('>=');
|
|
await user.click(regexOp);
|
|
const minDurationFilter = query.filters.find((f) => f.id === 'min-duration');
|
|
expect(minDurationFilter).not.toBeNull();
|
|
expect(minDurationFilter?.operator).toBe('>=');
|
|
}
|
|
});
|
|
|
|
it('should add new filter when new value is selected in the service name section', async () => {
|
|
const { container } = render(
|
|
<TraceQLSearch datasource={datasource} query={query} onChange={onChange} onClearResults={onClearResults} />
|
|
);
|
|
const serviceNameValue = container.querySelector(`input[aria-label="select service-name value"]`);
|
|
expect(serviceNameValue).not.toBeNull();
|
|
expect(serviceNameValue).toBeInTheDocument();
|
|
|
|
expect(query.filters.find((f) => f.id === 'service-name')).not.toBeDefined();
|
|
|
|
if (serviceNameValue) {
|
|
await user.click(serviceNameValue);
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(1000);
|
|
});
|
|
const customerValue = await screen.findByText('customer');
|
|
await user.click(customerValue);
|
|
const nameFilter = query.filters.find((f) => f.id === 'service-name');
|
|
expect(nameFilter).not.toBeNull();
|
|
expect(nameFilter?.operator).toBe('=');
|
|
expect(nameFilter?.value).toStrictEqual(['customer']);
|
|
expect(nameFilter?.tag).toBe('service.name');
|
|
expect(nameFilter?.scope).toBe(TraceqlSearchScope.Resource);
|
|
}
|
|
});
|
|
|
|
it('should not render static filter when no tag is configured', async () => {
|
|
const datasource: TempoDatasource = {
|
|
search: {
|
|
filters: [
|
|
{
|
|
id: 'service-name',
|
|
operator: '=',
|
|
scope: TraceqlSearchScope.Resource,
|
|
},
|
|
],
|
|
},
|
|
} as TempoDatasource;
|
|
datasource.isStreamingSearchEnabled = () => false;
|
|
datasource.isStreamingMetricsEnabled = () => false;
|
|
|
|
const lp = new TempoLanguageProvider(datasource);
|
|
lp.getIntrinsics = () => ['duration'];
|
|
lp.generateQueryFromFilters = () => '{}';
|
|
datasource.languageProvider = lp;
|
|
await act(async () => {
|
|
const { container } = render(
|
|
<TraceQLSearch datasource={datasource} query={query} onChange={onChange} onClearResults={onClearResults} />
|
|
);
|
|
const serviceNameValue = container.querySelector(`input[aria-label="select service-name value"]`);
|
|
expect(serviceNameValue).toBeNull();
|
|
expect(serviceNameValue).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should not render group by when feature toggle is not enabled', async () => {
|
|
await waitFor(() => {
|
|
render(
|
|
<TraceQLSearch datasource={datasource} query={query} onChange={onChange} onClearResults={onClearResults} />
|
|
);
|
|
const groupBy = screen.queryByText('Aggregate by');
|
|
expect(groupBy).toBeNull();
|
|
expect(groupBy).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render group by when feature toggle enabled', async () => {
|
|
config.featureToggles.metricsSummary = true;
|
|
await waitFor(() => {
|
|
render(
|
|
<TraceQLSearch datasource={datasource} query={query} onChange={onChange} onClearResults={onClearResults} />
|
|
);
|
|
const groupBy = screen.queryByText('Aggregate by');
|
|
expect(groupBy).not.toBeNull();
|
|
expect(groupBy).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|