Loki: Fix tests for test decoupling (#108678)

* Loki: Fix tests for test decoupling

* Update

* Add test and fix implementarion

* Remove empty line

* prettier
This commit is contained in:
Ivana Huckova
2025-07-29 16:32:09 +02:00
committed by GitHub
parent 5ce827ce4c
commit 66be589d86
7 changed files with 64 additions and 53 deletions
@@ -1,4 +1,4 @@
import { AbstractLabelOperator, DataFrame, TimeRange, dateTime, getDefaultTimeRange, ScopedVars } from '@grafana/data';
import { AbstractLabelOperator, DataFrame, TimeRange, dateTime, ScopedVars } from '@grafana/data';
import { config } from '@grafana/runtime';
import LanguageProvider from './LanguageProvider';
@@ -25,18 +25,6 @@ const mockTimeRange = {
},
};
jest.mock('@grafana/data', () => ({
...jest.requireActual('@grafana/data'),
getDefaultTimeRange: jest.fn().mockImplementation(() => ({
from: dateTime(0),
to: dateTime(1),
raw: {
from: dateTime(0),
to: dateTime(1),
},
})),
}));
describe('Language completion provider', () => {
describe('start', () => {
const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
@@ -208,8 +196,16 @@ describe('Language completion provider', () => {
.mockImplementation((range: TimeRange) => ({ start: range.from.valueOf(), end: range.to.valueOf() }));
const languageProvider = new LanguageProvider(datasource);
languageProvider.request = jest.fn().mockResolvedValue([]);
jest.spyOn(languageProvider, 'getDefaultTimeRange').mockImplementation(() => ({
from: dateTime(0),
to: dateTime(1),
raw: {
from: dateTime(0),
to: dateTime(1),
},
}));
languageProvider.fetchLabelValues('testKey');
expect(getDefaultTimeRange).toHaveBeenCalled();
expect(languageProvider.getDefaultTimeRange).toHaveBeenCalled();
expect(languageProvider.request).toHaveBeenCalled();
expect(languageProvider.request).toHaveBeenCalledWith('label/testKey/values', {
end: 1,
@@ -765,7 +761,7 @@ describe('Query imports', () => {
maxLines: DEFAULT_MAX_LINES_SAMPLE,
refId: 'data-samples',
},
getDefaultTimeRange()
languageProvider.getDefaultTimeRange()
);
});
@@ -786,7 +782,7 @@ describe('Query imports', () => {
maxLines: 5,
refId: 'data-samples',
},
getDefaultTimeRange()
languageProvider.getDefaultTimeRange()
);
});
@@ -511,7 +511,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
*
* @returns {TimeRange} The default time range
*/
private getDefaultTimeRange(): TimeRange {
getDefaultTimeRange(): TimeRange {
return getDefaultTimeRange();
}
}
@@ -1,5 +1,4 @@
import { of } from 'rxjs';
import { initTemplateSrv } from 'test/helpers/initTemplateSrv';
import {
DataQueryResponse,
@@ -9,7 +8,7 @@ import {
createDataFrame,
dateTime,
} from '@grafana/data';
import { setTemplateSrv } from '@grafana/runtime';
import { setTemplateSrv, TemplateSrv } from '@grafana/runtime';
import LokiLanguageProvider from './LanguageProvider';
import {
@@ -66,8 +65,10 @@ const frameWithoutTypes = {
describe('LogContextProvider', () => {
let logContextProvider: LogContextProvider;
beforeEach(() => {
const templateSrv = initTemplateSrv('key', [{ type: 'query', name: 'foo', current: { value: 'baz' } }]);
setTemplateSrv(templateSrv);
const templateSrv = {
replace: jest.fn((str: string) => str?.replace('$test', 'baz')),
};
setTemplateSrv(templateSrv as unknown as TemplateSrv);
const defaultDatasourceMock = createLokiDatasource(templateSrv);
defaultDatasourceMock.query = jest.fn(() => of({ data: [] } as DataQueryResponse));
defaultDatasourceMock.languageProvider = defaultLanguageProviderMock;
@@ -1772,6 +1772,20 @@ describe('LokiDatasource', () => {
})
);
});
it('calls query if convertMetricQueryToLogQuery is true for a metric query', async () => {
const spy = jest.spyOn(ds, 'query').mockImplementation(() => of({} as DataQueryResponse));
await ds.getDataSamples({ expr: 'rate({a="b"}[1m])', refId: 'A' }, mockTimeRange, {
convertMetricQueryToLogQuery: true,
});
expect(spy).toHaveBeenCalled();
});
it('does not call query if convertMetricQueryToLogQuery is false for a metric query', async () => {
const spy = jest.spyOn(ds, 'query');
await ds.getDataSamples({ expr: 'rate({a="b"}[1m])', refId: 'A' }, mockTimeRange, {
convertMetricQueryToLogQuery: false,
});
expect(spy).not.toHaveBeenCalled();
});
});
describe('Query splitting', () => {
@@ -739,14 +739,28 @@ export class LokiDatasource
* Currently, it works for logs data only.
* @returns A Promise that resolves to an array of DataFrames containing data samples.
*/
async getDataSamples(query: LokiQuery, timeRange: TimeRange): Promise<DataFrame[]> {
// Currently works only for logs sample
if (!isLogsQuery(query.expr) || isQueryWithError(this.interpolateString(query.expr, placeHolderScopedVars))) {
async getDataSamples(
query: LokiQuery,
timeRange: TimeRange,
options?: { convertMetricQueryToLogQuery?: boolean }
): Promise<DataFrame[]> {
let queryExpr = query.expr;
if (isQueryWithError(this.interpolateString(queryExpr, placeHolderScopedVars))) {
return [];
}
if (!isLogsQuery(queryExpr)) {
// If it is not a logs query, we need to check if we need to convert it to a logs query
if (options?.convertMetricQueryToLogQuery) {
queryExpr = getLogQueryFromMetricsQuery(queryExpr);
} else {
// Otherwise, we return an empty array, as data samples are only supported for logs queries
return [];
}
}
const lokiLogsQuery: LokiQuery = {
expr: query.expr,
expr: queryExpr,
queryType: LokiQueryType.Range,
refId: REF_ID_DATA_SAMPLES,
maxLines: query.maxLines || DEFAULT_MAX_LINES_SAMPLE,
@@ -4,8 +4,6 @@ import { DataQueryRequest, dateTime, LoadingState } from '@grafana/data';
import { config } from '@grafana/runtime';
import { LokiDatasource } from './datasource';
import * as logsTimeSplit from './logsTimeSplitting';
import * as metricTimeSplit from './metricTimeSplitting';
import { createLokiDatasource } from './mocks/datasource';
import { getMockFrames } from './mocks/frames';
import { runSplitQuery } from './querySplitting';
@@ -282,18 +280,12 @@ describe('runSplitQuery()', () => {
describe('Hidden and empty queries', () => {
beforeAll(() => {
jest.spyOn(logsTimeSplit, 'splitTimeRange').mockReturnValue([]);
jest.spyOn(metricTimeSplit, 'splitTimeRange').mockReturnValue([]);
jest.useFakeTimers().setSystemTime(new Date('Wed May 17 2023 17:20:12 GMT+0200'));
});
beforeEach(() => {
jest.mocked(logsTimeSplit.splitTimeRange).mockClear();
jest.mocked(logsTimeSplit.splitTimeRange).mockClear();
jest.mocked(trackGroupedQueries).mockClear();
});
afterAll(() => {
jest.mocked(logsTimeSplit.splitTimeRange).mockRestore();
jest.mocked(metricTimeSplit.splitTimeRange).mockRestore();
jest.useRealTimers();
});
test('Ignores hidden queries', async () => {
@@ -302,8 +294,6 @@ describe('runSplitQuery()', () => {
{ expr: '{a="b"}', refId: 'B' },
]);
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
expect(logsTimeSplit.splitTimeRange).toHaveBeenCalled();
expect(metricTimeSplit.splitTimeRange).not.toHaveBeenCalled();
expect(trackGroupedQueries).toHaveBeenCalledTimes(1);
expect(trackGroupedQueries).toHaveBeenCalledWith(
{
@@ -313,9 +303,10 @@ describe('runSplitQuery()', () => {
},
[
{
partition: [],
partition: expect.any(Array),
request: {
...request,
// Only includes queries that are not hidden
targets: request.targets.filter((query) => !query.hide),
},
},
@@ -331,8 +322,6 @@ describe('runSplitQuery()', () => {
{ expr: '', refId: 'B' },
]);
await expect(runSplitQuery(datasource, request)).toEmitValuesWith(() => {
expect(logsTimeSplit.splitTimeRange).not.toHaveBeenCalled();
expect(metricTimeSplit.splitTimeRange).toHaveBeenCalled();
expect(trackGroupedQueries).toHaveBeenCalledTimes(1);
expect(trackGroupedQueries).toHaveBeenCalledWith(
{
@@ -342,9 +331,10 @@ describe('runSplitQuery()', () => {
},
[
{
partition: [],
partition: expect.any(Array),
request: {
...request,
// Only includes queries with an expression
targets: request.targets.filter((query) => query.expr),
},
},
@@ -1,12 +1,9 @@
import { useState } from 'react';
import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
import { DataSourceApi, SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
import { QueryBuilderOperationParamEditorProps, VisualQueryModeller } from '@grafana/plugin-ui';
import { Select } from '@grafana/ui';
import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation';
import { LokiDatasource } from '../../datasource';
import { getLogQueryFromMetricsQuery, isQueryWithError } from '../../queryUtils';
import { extractUnwrapLabelKeysFromDataFrame } from '../../responseUtils';
import { getOperationParamId } from '../operationUtils';
import { LokiVisualQuery } from '../types';
@@ -31,11 +28,9 @@ export function UnwrapParamEditor({
inputId={getOperationParamId(operationId, index)}
onOpenMenu={async () => {
// This check is always true, we do it to make typescript happy
if (datasource instanceof LokiDatasource) {
setState({ isLoading: true });
const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange);
setState({ options, isLoading: undefined });
}
setState({ isLoading: true });
const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange);
setState({ options, isLoading: undefined });
}}
isLoading={state.isLoading}
allowCustomValue
@@ -54,17 +49,18 @@ export function UnwrapParamEditor({
async function loadUnwrapOptions(
query: LokiVisualQuery,
datasource: LokiDatasource,
datasource: DataSourceApi,
queryModeller: VisualQueryModeller,
timeRange = getDefaultTimeRange()
): Promise<Array<SelectableValue<string>>> {
const queryExpr = queryModeller.renderQuery(query);
const logExpr = getLogQueryFromMetricsQuery(queryExpr);
if (isQueryWithError(datasource.interpolateString(logExpr, placeHolderScopedVars))) {
if (!('getDataSamples' in datasource) || typeof datasource.getDataSamples !== 'function') {
return [];
}
const samples = await datasource.getDataSamples({ expr: logExpr, refId: 'unwrap_samples' }, timeRange);
// the query is a metric query, we need to set metricQueryToLogConversion to true to getSamples use the log query
const samples = await datasource.getDataSamples({ expr: queryExpr, refId: 'unwrap_samples' }, timeRange, {
convertMetricQueryToLogQuery: true,
});
const unwrapLabels = extractUnwrapLabelKeysFromDataFrame(samples[0]);
const labelOptions = unwrapLabels.map((label) => ({