grafana/public/app/features/explore/utils/supplementaryQueries.test.ts
Ivana Huckova 33186e3e23
Explore: Fix using data source line limit when opening logs sample in split view (#66601)
* Logs sample: Use data source max lines setting when opening logs in split view

* Add internal tags
2023-04-19 17:04:47 +02:00

362 lines
12 KiB
TypeScript

import { flatten } from 'lodash';
import { from, Observable } from 'rxjs';
import {
DataFrame,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceWithSupplementaryQueriesSupport,
FieldType,
LoadingState,
LogLevel,
LogsVolumeType,
MutableDataFrame,
SupplementaryQueryType,
SupplementaryQueryOptions,
toDataFrame,
} from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { MockDataSourceApi } from '../../../../test/mocks/datasource_srv';
import { MockDataQueryRequest, MockQuery } from '../../../../test/mocks/query';
import { ExplorePanelData } from '../../../types';
import { mockExplorePanelData } from '../__mocks__/data';
import { getSupplementaryQueryProvider } from './supplementaryQueries';
class MockDataSourceWithSupplementaryQuerySupport
extends MockDataSourceApi
implements DataSourceWithSupplementaryQueriesSupport<DataQuery>
{
private supplementaryQueriesResults: Record<SupplementaryQueryType, DataFrame[] | undefined> = {
[SupplementaryQueryType.LogsVolume]: undefined,
[SupplementaryQueryType.LogsSample]: undefined,
};
withSupplementaryQuerySupport(type: SupplementaryQueryType, data: DataFrame[]) {
this.supplementaryQueriesResults[type] = data;
return this;
}
getDataProvider(
type: SupplementaryQueryType,
request: DataQueryRequest<DataQuery>
): Observable<DataQueryResponse> | undefined {
const data = this.supplementaryQueriesResults[type];
if (data) {
return from([
{ state: LoadingState.Loading, data: [] },
{ state: LoadingState.Done, data },
]);
}
return undefined;
}
getSupplementaryQuery(options: SupplementaryQueryOptions, query: DataQuery): DataQuery | undefined {
return query;
}
getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] {
return Object.values(SupplementaryQueryType).filter((type) => this.supplementaryQueriesResults[type]);
}
}
const createSupplementaryQueryResponse = (type: SupplementaryQueryType, id: string) => {
return [
toDataFrame({
refId: `1-${type}-${id}`,
fields: [{ name: 'value', type: FieldType.string, values: [1] }],
meta: {
custom: {
logsVolumeType: LogsVolumeType.FullRange,
},
},
}),
toDataFrame({
refId: `2-${type}-${id}`,
fields: [{ name: 'value', type: FieldType.string, values: [2] }],
meta: {
custom: {
logsVolumeType: LogsVolumeType.FullRange,
},
},
}),
];
};
const mockRow = (refId: string) => {
return {
rowIndex: 0,
entryFieldIndex: 0,
dataFrame: new MutableDataFrame({ refId, fields: [{ name: 'A', values: [] }] }),
entry: '',
hasAnsi: false,
hasUnescapedContent: false,
labels: {},
logLevel: LogLevel.info,
raw: '',
timeEpochMs: 0,
timeEpochNs: '0',
timeFromNow: '',
timeLocal: '',
timeUtc: '',
uid: '1',
};
};
const mockExploreDataWithLogs = () =>
mockExplorePanelData({
logsResult: {
rows: [mockRow('0'), mockRow('1')],
visibleRange: { from: 0, to: 1 },
bucketSize: 1000,
},
});
const datasources: DataSourceApi[] = [
new MockDataSourceWithSupplementaryQuerySupport('logs-volume-a').withSupplementaryQuerySupport(
SupplementaryQueryType.LogsVolume,
createSupplementaryQueryResponse(SupplementaryQueryType.LogsVolume, 'logs-volume-a')
),
new MockDataSourceWithSupplementaryQuerySupport('logs-volume-b').withSupplementaryQuerySupport(
SupplementaryQueryType.LogsVolume,
createSupplementaryQueryResponse(SupplementaryQueryType.LogsVolume, 'logs-volume-b')
),
new MockDataSourceWithSupplementaryQuerySupport('logs-sample-a').withSupplementaryQuerySupport(
SupplementaryQueryType.LogsSample,
createSupplementaryQueryResponse(SupplementaryQueryType.LogsSample, 'logs-sample-a')
),
new MockDataSourceWithSupplementaryQuerySupport('logs-sample-b').withSupplementaryQuerySupport(
SupplementaryQueryType.LogsSample,
createSupplementaryQueryResponse(SupplementaryQueryType.LogsSample, 'logs-sample-b')
),
new MockDataSourceApi('no-data-providers'),
new MockDataSourceApi('no-data-providers-2'),
];
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getDataSourceSrv: () => {
return {
get: async ({ uid }: { uid: string }) => datasources.find((ds) => ds.name === uid) || undefined,
};
},
}));
const setup = async (targetSources: string[], type: SupplementaryQueryType) => {
const requestMock = new MockDataQueryRequest({
targets: targetSources.map((source, i) => new MockQuery(`${i}`, 'a', { uid: source })),
});
const explorePanelDataMock: Observable<ExplorePanelData> = mockExploreDataWithLogs();
const datasources = await Promise.all(
targetSources.map(async (source, i) => {
const datasource = await getDataSourceSrv().get({ uid: source });
return {
datasource,
targets: [new MockQuery(`${i}`, 'a', { uid: source })],
};
})
);
return getSupplementaryQueryProvider(datasources, type, requestMock, explorePanelDataMock);
};
const assertDataFrom = (type: SupplementaryQueryType, ...datasources: string[]) => {
return flatten(
datasources.map((name: string) => {
return [{ refId: `1-${type}-${name}` }, { refId: `2-${type}-${name}` }];
})
);
};
const assertDataFromLogsResults = () => {
return [{ meta: { custom: { logsVolumeType: LogsVolumeType.Limited } } }];
};
describe('SupplementaryQueries utils', function () {
describe('Non-mixed data source', function () {
it('Returns result from the provider', async () => {
const testProvider = await setup(['logs-volume-a'], SupplementaryQueryType.LogsVolume);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{ data: [], state: LoadingState.Loading },
{
data: assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a'),
state: LoadingState.Done,
},
]);
});
});
it('Uses fallback for logs volume', async () => {
const testProvider = await setup(['no-data-providers'], SupplementaryQueryType.LogsVolume);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{
data: assertDataFromLogsResults(),
state: LoadingState.Done,
},
]);
});
});
it('Returns undefined for logs sample', async () => {
const testProvider = await setup(['no-data-providers'], SupplementaryQueryType.LogsSample);
await expect(testProvider).toBe(undefined);
});
it('Creates single fallback result', async () => {
const testProvider = await setup(['no-data-providers', 'no-data-providers-2'], SupplementaryQueryType.LogsVolume);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{
data: assertDataFromLogsResults(),
state: LoadingState.Done,
},
{
data: [...assertDataFromLogsResults(), ...assertDataFromLogsResults()],
state: LoadingState.Done,
},
]);
});
});
});
describe('Mixed data source', function () {
describe('Logs volume', function () {
describe('All data sources support full range logs volume', function () {
it('Merges all data frames into a single response', async () => {
const testProvider = await setup(['logs-volume-a', 'logs-volume-b'], SupplementaryQueryType.LogsVolume);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{ data: [], state: LoadingState.Loading },
{
data: assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a'),
state: LoadingState.Done,
},
{
data: assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a', 'logs-volume-b'),
state: LoadingState.Done,
},
]);
});
});
});
describe('All data sources do not support full range logs volume', function () {
it('Creates single fallback result', async () => {
const testProvider = await setup(
['no-data-providers', 'no-data-providers-2'],
SupplementaryQueryType.LogsVolume
);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{
data: assertDataFromLogsResults(),
state: LoadingState.Done,
},
{
data: [...assertDataFromLogsResults(), ...assertDataFromLogsResults()],
state: LoadingState.Done,
},
]);
});
});
});
describe('Some data sources support full range logs volume, while others do not', function () {
it('Creates merged result containing full range and limited logs volume', async () => {
const testProvider = await setup(
['logs-volume-a', 'no-data-providers', 'logs-volume-b', 'no-data-providers-2'],
SupplementaryQueryType.LogsVolume
);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{
data: [],
state: LoadingState.Loading,
},
{
data: assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a'),
state: LoadingState.Done,
},
{
data: [
...assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a'),
...assertDataFromLogsResults(),
],
state: LoadingState.Done,
},
{
data: [
...assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-a'),
...assertDataFromLogsResults(),
...assertDataFrom(SupplementaryQueryType.LogsVolume, 'logs-volume-b'),
],
state: LoadingState.Done,
},
]);
});
});
});
});
describe('Logs sample', function () {
describe('All data sources support logs sample', function () {
it('Merges all responses into single result', async () => {
const testProvider = await setup(['logs-sample-a', 'logs-sample-b'], SupplementaryQueryType.LogsSample);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{ data: [], state: LoadingState.Loading },
{
data: assertDataFrom(SupplementaryQueryType.LogsSample, 'logs-sample-a'),
state: LoadingState.Done,
},
{
data: assertDataFrom(SupplementaryQueryType.LogsSample, 'logs-sample-a', 'logs-sample-b'),
state: LoadingState.Done,
},
]);
});
});
});
describe('All data sources do not support full range logs volume', function () {
it('Does not provide fallback result', async () => {
const testProvider = await setup(
['no-data-providers', 'no-data-providers-2'],
SupplementaryQueryType.LogsSample
);
await expect(testProvider).toBeUndefined();
});
});
describe('Some data sources support full range logs volume, while others do not', function () {
it('Returns results only for data sources supporting logs sample', async () => {
const testProvider = await setup(
['logs-sample-a', 'no-data-providers', 'logs-sample-b', 'no-data-providers-2'],
SupplementaryQueryType.LogsSample
);
await expect(testProvider).toEmitValuesWith((received) => {
expect(received).toMatchObject([
{ data: [], state: LoadingState.Loading },
{
data: assertDataFrom(SupplementaryQueryType.LogsSample, 'logs-sample-a'),
state: LoadingState.Done,
},
{
data: assertDataFrom(SupplementaryQueryType.LogsSample, 'logs-sample-a', 'logs-sample-b'),
state: LoadingState.Done,
},
]);
});
});
});
});
});
});