From 9def0d2305d81cb3cd4ee0ca2b2143a0c856a974 Mon Sep 17 00:00:00 2001 From: Gareth Dawson Date: Wed, 20 Sep 2023 16:47:21 +0100 Subject: [PATCH] Loki: Fix timerange for query stats request (#72193) * call query stats with whole query * call stats when query type changes * fix stats request timerange * feedback: extract functions from component * feedback: remove "or string" types * feedback: use ds timerange picker end * partial: fix broken test * changes * update test comment * rename variable * add comment explain message prop * make getStatsTimeRange a datasource method * update tests for getStatsTimeRange * make getStats a datasource method * update failing tests * update failing tests * remove import * Update public/app/plugins/datasource/loki/datasource.test.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --------- Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../loki/components/LokiQueryEditor.tsx | 26 ++-- .../components/LokiQueryEditorByApp.test.tsx | 16 +-- .../loki/components/LokiQueryField.tsx | 4 +- .../MonacoQueryField.test.tsx | 8 +- .../monaco-query-field/MonacoQueryField.tsx | 16 +-- .../MonacoQueryFieldProps.ts | 2 +- .../MonacoQueryFieldWrapper.tsx | 2 +- .../datasource/loki/components/stats.test.ts | 71 ++-------- .../datasource/loki/components/stats.ts | 23 +-- .../datasource/loki/datasource.test.ts | 131 +++++++++++++++++- .../app/plugins/datasource/loki/datasource.ts | 79 ++++++++++- .../components/LokiQueryCodeEditor.tsx | 8 -- public/app/plugins/datasource/loki/types.ts | 2 + .../querybuilder/shared/QueryOptionGroup.tsx | 22 ++- 14 files changed, 266 insertions(+), 144 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index 9e3a407178e..291e3cadf1c 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -21,7 +21,7 @@ import { buildVisualQueryFromString } from '../querybuilder/parsing'; import { changeEditorMode, getQueryWithDefaults } from '../querybuilder/state'; import { LokiQuery, QueryStats } from '../types'; -import { getStats, shouldUpdateStats } from './stats'; +import { shouldUpdateStats } from './stats'; import { LokiQueryEditorProps } from './types'; export const testIds = { @@ -45,7 +45,8 @@ export const LokiQueryEditor = React.memo((props) => { if (config.featureToggles.lokiPredefinedOperations && !query.expr && predefinedOperations) { query.expr = `{} ${predefinedOperations}`; } - const previousQuery = usePrevious(query.expr); + const previousQueryExpr = usePrevious(query.expr); + const previousQueryType = usePrevious(query.queryType); // This should be filled in from the defaults by now. const editorMode = query.editorMode!; @@ -96,15 +97,22 @@ export const LokiQueryEditor = React.memo((props) => { }; useEffect(() => { - const update = shouldUpdateStats(query.expr, previousQuery, timerange, previousTimerange); + const update = shouldUpdateStats( + query.expr, + previousQueryExpr, + timerange, + previousTimerange, + query.queryType, + previousQueryType + ); if (update) { const makeAsyncRequest = async () => { - const stats = await getStats(datasource, query.expr); + const stats = await datasource.getStats(query); setQueryStats(stats); }; makeAsyncRequest(); } - }, [datasource, timerange, previousTimerange, query, previousQuery, setQueryStats]); + }, [datasource, timerange, previousTimerange, query, previousQueryExpr, previousQueryType, setQueryStats]); return ( <> @@ -180,13 +188,7 @@ export const LokiQueryEditor = React.memo((props) => { {editorMode === QueryEditorMode.Code && ( - + )} {editorMode === QueryEditorMode.Builder && ( Promise.resolve([]), - getSyntax: () => {}, - getLabelKeys: () => [], - metrics: [], - }, - getQueryHints: () => [], - getDataSamples: () => [], - maxLines: 20, - getTimeRange: jest.fn(), - } as unknown as LokiDatasource; + const dataSource = createLokiDatasource(); + dataSource.metadataRequest = jest.fn(); return render( void; } interface LokiQueryFieldState { @@ -66,7 +65,7 @@ export class LokiQueryField extends React.PureComponent diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx index a910e4112a1..b89ab68fdc4 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.test.tsx @@ -6,7 +6,12 @@ import { createLokiDatasource } from '../../mocks'; import MonacoQueryField from './MonacoQueryField'; import { Props } from './MonacoQueryFieldProps'; -function renderComponent({ initialValue = '', onRunQuery = jest.fn(), onBlur = jest.fn() }: Partial = {}) { +function renderComponent({ + initialValue = '', + onRunQuery = jest.fn(), + onBlur = jest.fn(), + onChange = jest.fn(), +}: Partial = {}) { const datasource = createLokiDatasource(); render( @@ -16,6 +21,7 @@ function renderComponent({ initialValue = '', onRunQuery = jest.fn(), onBlur = j history={[]} onRunQuery={onRunQuery} onBlur={onBlur} + onChange={onChange} placeholder="Enter a Loki query (run with Shift+Enter)" /> ); diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx index f389d4c24a1..5b077651eba 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryField.tsx @@ -91,15 +91,7 @@ const getStyles = (theme: GrafanaTheme2, placeholder: string) => { }; }; -const MonacoQueryField = ({ - history, - onBlur, - onRunQuery, - initialValue, - datasource, - placeholder, - onQueryType, -}: Props) => { +const MonacoQueryField = ({ history, onBlur, onRunQuery, initialValue, datasource, placeholder, onChange }: Props) => { const id = uuidv4(); // we need only one instance of `overrideServices` during the lifetime of the react component const overrideServicesRef = useRef(getOverrideServices()); @@ -151,11 +143,7 @@ const MonacoQueryField = ({ }; const onTypeDebounced = debounce(async (query: string) => { - if (!onQueryType) { - return; - } - - onQueryType(query); + onChange(query); }, 1000); return ( diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldProps.ts b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldProps.ts index e454d22e94d..d52c126ceed 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldProps.ts +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldProps.ts @@ -14,5 +14,5 @@ export type Props = { onBlur: (value: string) => void; placeholder: string; datasource: LokiDatasource; - onQueryType?: (query: string) => void; + onChange: (query: string) => void; }; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldWrapper.tsx b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldWrapper.tsx index f4ac1b73774..0ca7d70f018 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldWrapper.tsx +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/MonacoQueryFieldWrapper.tsx @@ -23,5 +23,5 @@ export const MonacoQueryFieldWrapper = (props: Props) => { onChange(value); }; - return ; + return ; }; diff --git a/public/app/plugins/datasource/loki/components/stats.test.ts b/public/app/plugins/datasource/loki/components/stats.test.ts index 2fb377600ee..89535558ef1 100644 --- a/public/app/plugins/datasource/loki/components/stats.test.ts +++ b/public/app/plugins/datasource/loki/components/stats.test.ts @@ -1,15 +1,18 @@ import { dateTime, getDefaultTimeRange } from '@grafana/data'; -import { createLokiDatasource } from '../mocks'; +import { LokiQueryType } from '../types'; -import { getStats, shouldUpdateStats } from './stats'; +import { shouldUpdateStats } from './stats'; describe('shouldUpdateStats', () => { const timerange = getDefaultTimeRange(); + let queryType = LokiQueryType.Range; + let prevQueryType = LokiQueryType.Range; + it('should return true if the query has changed', () => { const query = '{job="grafana"}'; const prevQuery = '{job="not-grafana"}'; - expect(shouldUpdateStats(query, prevQuery, timerange, timerange)).toBe(true); + expect(shouldUpdateStats(query, prevQuery, timerange, timerange, queryType, prevQueryType)).toBe(true); }); it('should return true if the timerange has changed', () => { @@ -17,25 +20,25 @@ describe('shouldUpdateStats', () => { const prevQuery = '{job="grafana"}'; timerange.raw.from = 'now-14h'; const prevTimerange = getDefaultTimeRange(); - expect(shouldUpdateStats(query, prevQuery, timerange, prevTimerange)).toBe(true); + expect(shouldUpdateStats(query, prevQuery, timerange, prevTimerange, queryType, prevQueryType)).toBe(true); }); it('should return true if the previous query was undefined', () => { const query = '{job="grafana"}'; const prevQuery = undefined; - expect(shouldUpdateStats(query, prevQuery, timerange, timerange)).toBe(true); + expect(shouldUpdateStats(query, prevQuery, timerange, timerange, queryType, prevQueryType)).toBe(true); }); it('should return true if the query really changed, otherwise false', () => { const prevQuery = '{job="grafana"}'; const query = `${prevQuery} `; - expect(shouldUpdateStats(query, prevQuery, timerange, timerange)).toBe(false); + expect(shouldUpdateStats(query, prevQuery, timerange, timerange, queryType, prevQueryType)).toBe(false); }); it('should return false if the query and timerange have not changed', () => { const query = '{job="grafana"}'; const prevQuery = '{job="grafana"}'; - expect(shouldUpdateStats(query, prevQuery, timerange, timerange)).toBe(false); + expect(shouldUpdateStats(query, prevQuery, timerange, timerange, queryType, prevQueryType)).toBe(false); }); it('should return false if the query and timerange with absolute and relative mixed have not changed', () => { @@ -46,57 +49,13 @@ describe('shouldUpdateStats', () => { const prevTimerange = getDefaultTimeRange(); prevTimerange.raw.from = now; - expect(shouldUpdateStats(query, prevQuery, timerange, prevTimerange)).toBe(false); + expect(shouldUpdateStats(query, prevQuery, timerange, prevTimerange, queryType, prevQueryType)).toBe(false); }); -}); -describe('makeStatsRequest', () => { - const datasource = createLokiDatasource(); - - it('should return null if there is no query', () => { - const query = ''; - expect(getStats(datasource, query)).resolves.toBe(null); - }); - - it('should return null if the query is invalid', () => { - const query = '{job="grafana",'; - expect(getStats(datasource, query)).resolves.toBe(null); - }); - - it('should return null if the response has no data', () => { - const query = '{job="grafana"}'; - datasource.getQueryStats = jest.fn().mockResolvedValue({ streams: 0, chunks: 0, bytes: 0, entries: 0 }); - expect(getStats(datasource, query)).resolves.toBe(null); - }); - - it('should return the stats if the response has data', () => { + it('should return true if the query type has changed', () => { const query = '{job="grafana"}'; - - datasource.getQueryStats = jest - .fn() - .mockResolvedValue({ streams: 1, chunks: 12611, bytes: 12913664, entries: 78344 }); - expect(getStats(datasource, query)).resolves.toEqual({ - streams: 1, - chunks: 12611, - bytes: 12913664, - entries: 78344, - }); - }); - - it('should support queries with variables', () => { - const query = 'count_over_time({job="grafana"}[$__interval])'; - - datasource.interpolateString = jest - .fn() - .mockImplementationOnce((value: string) => value.replace('$__interval', '1h')); - datasource.getQueryStats = jest - .fn() - .mockResolvedValue({ streams: 1, chunks: 12611, bytes: 12913664, entries: 78344 }); - expect(getStats(datasource, query)).resolves.toEqual({ - streams: 1, - chunks: 12611, - bytes: 12913664, - entries: 78344, - }); + const prevQuery = '{job="grafana"}'; + prevQueryType = LokiQueryType.Instant; + expect(shouldUpdateStats(query, prevQuery, timerange, timerange, queryType, prevQueryType)).toBe(true); }); }); diff --git a/public/app/plugins/datasource/loki/components/stats.ts b/public/app/plugins/datasource/loki/components/stats.ts index 10bbf18588a..232b33e9a81 100644 --- a/public/app/plugins/datasource/loki/components/stats.ts +++ b/public/app/plugins/datasource/loki/components/stats.ts @@ -1,21 +1,6 @@ import { DateTime, isDateTime, TimeRange } from '@grafana/data'; -import { LokiDatasource } from '../datasource'; -import { QueryStats } from '../types'; - -export async function getStats(datasource: LokiDatasource, query: string): Promise { - if (!query) { - return null; - } - - const response = await datasource.getQueryStats(query); - - if (!response) { - return null; - } - - return Object.values(response).every((v) => v === 0) ? null : response; -} +import { LokiQueryType } from '../types'; /** * This function compares two time values. If the first is absolute, it compares them using `DateTime.isSame`. @@ -37,9 +22,11 @@ export function shouldUpdateStats( query: string, prevQuery: string | undefined, timerange: TimeRange, - prevTimerange: TimeRange | undefined + prevTimerange: TimeRange | undefined, + queryType: LokiQueryType | undefined, + prevQueryType: LokiQueryType | undefined ): boolean { - if (prevQuery === undefined || query.trim() !== prevQuery.trim()) { + if (prevQuery === undefined || query.trim() !== prevQuery.trim() || queryType !== prevQueryType) { return true; } diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 378b1c44743..00f795d645e 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1288,21 +1288,26 @@ describe('LokiDatasource', () => { describe('getQueryStats', () => { let ds: LokiDatasource; + let query: LokiQuery; beforeEach(() => { ds = createLokiDatasource(templateSrvStub); ds.statsMetadataRequest = jest.fn().mockResolvedValue({ streams: 1, chunks: 1, bytes: 1, entries: 1 }); ds.interpolateString = jest.fn().mockImplementation((value: string) => value.replace('$__interval', '1m')); + + query = { refId: 'A', expr: '', queryType: LokiQueryType.Range }; }); it('uses statsMetadataRequest', async () => { - const result = await ds.getQueryStats('{foo="bar"}'); + query.expr = '{foo="bar"}'; + const result = await ds.getQueryStats(query); expect(ds.statsMetadataRequest).toHaveBeenCalled(); expect(result).toEqual({ streams: 1, chunks: 1, bytes: 1, entries: 1 }); }); it('supports queries with template variables', async () => { - const result = await ds.getQueryStats('rate({instance="server\\1"}[$__interval])'); + query.expr = 'rate({instance="server\\1"}[$__interval])'; + const result = await ds.getQueryStats(query); expect(result).toEqual({ streams: 1, @@ -1313,14 +1318,16 @@ describe('LokiDatasource', () => { }); it('does not call stats if the query is invalid', async () => { - const result = await ds.getQueryStats('rate({label="value"}'); + query.expr = 'rate({label="value"}'; + const result = await ds.getQueryStats(query); expect(ds.statsMetadataRequest).not.toHaveBeenCalled(); expect(result).toBe(undefined); }); it('combines the stats of each label matcher', async () => { - const result = await ds.getQueryStats('count_over_time({foo="bar"}[1m]) + count_over_time({test="test"}[1m])'); + query.expr = 'count_over_time({foo="bar"}[1m]) + count_over_time({test="test"}[1m])'; + const result = await ds.getQueryStats(query); expect(ds.statsMetadataRequest).toHaveBeenCalled(); expect(result).toEqual({ streams: 2, chunks: 2, bytes: 2, entries: 2 }); @@ -1424,6 +1431,122 @@ describe('applyTemplateVariables', () => { ); }); }); + + describe('getStatsTimeRange', () => { + let query: LokiQuery; + let datasource: LokiDatasource; + + beforeEach(() => { + query = { refId: 'A', expr: '', queryType: LokiQueryType.Range }; + datasource = createLokiDatasource(); + + datasource.getTimeRangeParams = jest.fn().mockReturnValue({ + start: 1672552800000000000, // 01 Jan 2023 06:00:00 GMT + end: 1672639200000000000, // 02 Jan 2023 06:00:00 GMT + }); + }); + + it('should return the ds picker timerange for a logs query with range type', () => { + // log queries with range type should request the ds picker timerange + // in this case (1 day) + query.expr = '{job="grafana"}'; + + expect(datasource.getStatsTimeRange(query, 0)).toEqual({ + start: 1672552800000000000, // 01 Jan 2023 06:00:00 GMT + end: 1672639200000000000, // 02 Jan 2023 06:00:00 GMT + }); + }); + + it('should return nothing for a logs query with instant type', () => { + // log queries with instant type should be invalid. + query.queryType = LokiQueryType.Instant; + query.expr = '{job="grafana"}'; + + expect(datasource.getStatsTimeRange(query, 0)).toEqual({ + start: undefined, + end: undefined, + }); + }); + + it('should return the ds picker timerange', () => { + // metric queries with range type should request ds picker timerange + // in this case (1 day) + query.expr = 'rate({job="grafana"}[5m])'; + + expect(datasource.getStatsTimeRange(query, 0)).toEqual({ + start: 1672552800000000000, // 01 Jan 2023 06:00:00 GMT + end: 1672639200000000000, // 02 Jan 2023 06:00:00 GMT + }); + }); + + it('should return the range duration for an instant metric query', () => { + // metric queries with instant type should request range duration + // in this case (5 minutes) + query.queryType = LokiQueryType.Instant; + query.expr = 'rate({job="grafana"}[5m])'; + + expect(datasource.getStatsTimeRange(query, 0)).toEqual({ + start: 1672638900000000000, // 02 Jan 2023 05:55:00 GMT + end: 1672639200000000000, // 02 Jan 2023 06:00:00 GMT + }); + }); + }); +}); + +describe('makeStatsRequest', () => { + const datasource = createLokiDatasource(); + let query: LokiQuery; + + beforeEach(() => { + query = { refId: 'A', expr: '', queryType: LokiQueryType.Range }; + }); + + it('should return null if there is no query', () => { + query.expr = ''; + expect(datasource.getStats(query)).resolves.toBe(null); + }); + + it('should return null if the query is invalid', () => { + query.expr = '{job="grafana",'; + expect(datasource.getStats(query)).resolves.toBe(null); + }); + + it('should return null if the response has no data', () => { + query.expr = '{job="grafana"}'; + datasource.getQueryStats = jest.fn().mockResolvedValue({ streams: 0, chunks: 0, bytes: 0, entries: 0 }); + expect(datasource.getStats(query)).resolves.toBe(null); + }); + + it('should return the stats if the response has data', () => { + query.expr = '{job="grafana"}'; + + datasource.getQueryStats = jest + .fn() + .mockResolvedValue({ streams: 1, chunks: 12611, bytes: 12913664, entries: 78344 }); + expect(datasource.getStats(query)).resolves.toEqual({ + streams: 1, + chunks: 12611, + bytes: 12913664, + entries: 78344, + }); + }); + + it('should support queries with variables', () => { + query.expr = 'count_over_time({job="grafana"}[$__interval])'; + + datasource.interpolateString = jest + .fn() + .mockImplementationOnce((value: string) => value.replace('$__interval', '1h')); + datasource.getQueryStats = jest + .fn() + .mockResolvedValue({ streams: 1, chunks: 12611, bytes: 12913664, entries: 78344 }); + expect(datasource.getStats(query)).resolves.toEqual({ + streams: 1, + chunks: 12611, + bytes: 12913664, + entries: 78344, + }); + }); }); describe('getTimeRange*()', () => { diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index a6ee2f8c731..8ca0dfdae5e 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -38,6 +38,8 @@ import { QueryFilterOptions, renderLegendFormat, } from '@grafana/data'; +import { intervalToMs } from '@grafana/data/src/datetime/rangeutil'; +import { Duration } from '@grafana/lezer-logql'; import { BackendSrvRequest, config, DataSourceWithBackend, FetchError } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { convertToWebSocketUrl } from 'app/core/utils/explore'; @@ -77,6 +79,7 @@ import { runSplitQuery } from './querySplitting'; import { getLogQueryFromMetricsQuery, getLokiQueryFromDataQuery, + getNodesFromQuery, getNormalizedLokiQuery, getStreamSelectorsFromQuery, isLogsQuery, @@ -454,22 +457,30 @@ export class LokiDatasource return await this.getResource(url, params, options); } - async getQueryStats(query: string): Promise { + async getQueryStats(query: LokiQuery): Promise { // if query is invalid, clear stats, and don't request - if (isQueryWithError(this.interpolateString(query, placeHolderScopedVars))) { + if (isQueryWithError(this.interpolateString(query.expr, placeHolderScopedVars))) { return undefined; } - const { start, end } = this.getTimeRangeParams(); - const labelMatchers = getStreamSelectorsFromQuery(query); - + const labelMatchers = getStreamSelectorsFromQuery(query.expr); let statsForAll: QueryStats = { streams: 0, chunks: 0, bytes: 0, entries: 0 }; - for (const labelMatcher of labelMatchers) { + for (const idx in labelMatchers) { + const { start, end } = this.getStatsTimeRange(query, Number(idx)); + + if (start === undefined || end === undefined) { + return { streams: 0, chunks: 0, bytes: 0, entries: 0, message: 'Query size estimate not available.' }; + } + try { const data = await this.statsMetadataRequest( 'index/stats', - { query: labelMatcher, start, end }, + { + query: labelMatchers[idx], + start: start, + end: end, + }, { showErrorAlert: false } ); @@ -487,6 +498,60 @@ export class LokiDatasource return statsForAll; } + getStatsTimeRange(query: LokiQuery, idx: number): { start: number | undefined; end: number | undefined } { + let start: number, end: number; + const NS_IN_MS = 1000000; + const durationNodes = getNodesFromQuery(query.expr, [Duration]); + const durations = durationNodes.map((d) => query.expr.substring(d.from, d.to)); + + if (isLogsQuery(query.expr)) { + // logs query with instant type can not be estimated + if (query.queryType === LokiQueryType.Instant) { + return { start: undefined, end: undefined }; + } + // logs query with range type + return this.getTimeRangeParams(); + } + + if (query.queryType === LokiQueryType.Instant) { + // metric query with instant type + + if (!!durations[idx]) { + // if query has a duration e.g. [1m] + end = this.getTimeRangeParams().end; + start = end - intervalToMs(durations[idx]) * NS_IN_MS; + return { start, end }; + } else { + // if query has no duration e.g. [$__interval] + + if (/(\$__auto|\$__range)/.test(query.expr)) { + // if $__auto or $__range is used, we can estimate the time range using the selected range + return this.getTimeRangeParams(); + } + + // otherwise we cant estimate the time range + return { start: undefined, end: undefined }; + } + } + + // metric query with range type + return this.getTimeRangeParams(); + } + + async getStats(query: LokiQuery): Promise { + if (!query) { + return null; + } + + const response = await this.getQueryStats(query); + + if (!response) { + return null; + } + + return Object.values(response).every((v) => v === 0) ? null : response; + } + async metricFindQuery(query: LokiVariableQuery | string) { if (!query) { return Promise.resolve([]); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx index 9ee1ea8cfe0..7e4fcc2ce3c 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx @@ -8,16 +8,13 @@ import { getModKey } from 'app/core/utils/browser'; import { testIds } from '../../components/LokiQueryEditor'; import { LokiQueryField } from '../../components/LokiQueryField'; -import { getStats } from '../../components/stats'; import { LokiQueryEditorProps } from '../../components/types'; import { formatLogqlQuery } from '../../queryUtils'; -import { QueryStats } from '../../types'; import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplained'; type Props = LokiQueryEditorProps & { showExplain: boolean; - setQueryStats: React.Dispatch>; }; export function LokiQueryCodeEditor({ @@ -30,7 +27,6 @@ export function LokiQueryCodeEditor({ app, showExplain, history, - setQueryStats, }: Props) { const styles = useStyles2(getStyles); @@ -49,10 +45,6 @@ export function LokiQueryCodeEditor({ data={data} app={app} data-testid={testIds.editor} - onQueryType={async (query: string) => { - const stats = await getStats(datasource, query); - setQueryStats(stats); - }} ExtraFieldElement={ <> {lokiFormatQuery && ( diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts index 3ee8e96fc19..2d06d3ac1f6 100644 --- a/public/app/plugins/datasource/loki/types.ts +++ b/public/app/plugins/datasource/loki/types.ts @@ -152,6 +152,8 @@ export interface QueryStats { chunks: number; bytes: number; entries: number; + // The error message displayed in the UI when we cant estimate the size of the query. + message?: string; } export interface ContextFilter { diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup.tsx b/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup.tsx index fad59f18c34..4963de23d99 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup.tsx @@ -19,11 +19,6 @@ export function QueryOptionGroup({ title, children, collapsedInfo, queryStats }: const [isOpen, toggleOpen] = useToggle(false); const styles = useStyles2(getStyles); - const convertUnits = (): string => { - const { text, suffix } = getValueFormat('bytes')(queryStats!.bytes, 1); - return text + suffix; - }; - return (
{children}
+ {queryStats && config.featureToggles.lokiQuerySplitting && ( )} - {queryStats &&

This query will process approximately {convertUnits()}.

} + + {queryStats &&

{generateQueryStats(queryStats)}

}
); } @@ -103,3 +100,16 @@ const getStyles = (theme: GrafanaTheme2) => { }), }; }; + +const generateQueryStats = (queryStats: QueryStats) => { + if (queryStats.message) { + return queryStats.message; + } + + return `This query will process approximately ${convertUnits(queryStats)}.`; +}; + +const convertUnits = (queryStats: QueryStats): string => { + const { text, suffix } = getValueFormat('bytes')(queryStats.bytes, 1); + return text + suffix; +};