Loki: Add lokiQueryHints feature flag (#78953)

* add `lokiQueryHints` feature flag

* language provider tests

* fix unwrap tests

* add feature toggle
This commit is contained in:
Sven Grossmann 2023-12-18 21:43:16 +01:00 committed by GitHub
parent 456939bac4
commit 2165c9b3f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 72 additions and 10 deletions

View File

@ -53,6 +53,7 @@ Some features are enabled by default. You can disable these feature by setting t
| `alertingInsights` | Show the new alerting insights landing page | Yes |
| `cloudWatchWildCardDimensionValues` | Fetches dimension values from CloudWatch to correctly label wildcard dimensions | Yes |
| `displayAnonymousStats` | Enables anonymous stats to be shown in the UI for Grafana | Yes |
| `lokiQueryHints` | Enables query hints for Loki | Yes |
## Preview feature toggles

View File

@ -169,4 +169,5 @@ export interface FeatureToggles {
regressionTransformation?: boolean;
displayAnonymousStats?: boolean;
alertStateHistoryAnnotationsFromLoki?: boolean;
lokiQueryHints?: boolean;
}

View File

@ -1270,5 +1270,16 @@ var (
RequiresRestart: true,
Created: time.Date(2023, time.November, 30, 12, 0, 0, 0, time.UTC),
},
{
// this is mainly used a a way to quickly disable query hints as a safe guard for our infrastructure
Name: "lokiQueryHints",
Description: "Enables query hints for Loki",
Stage: FeatureStageGeneralAvailability,
FrontendOnly: true,
Expression: "true",
Owner: grafanaObservabilityLogsSquad,
AllowSelfServe: false,
Created: time.Date(2023, time.December, 18, 12, 0, 0, 0, time.UTC),
},
}
)

View File

@ -150,3 +150,4 @@ tableSharedCrosshair,experimental,@grafana/grafana-bi-squad,2023-12-12,false,fal
regressionTransformation,privatePreview,@grafana/grafana-bi-squad,2023-11-24,false,false,false,true
displayAnonymousStats,GA,@grafana/identity-access-team,2023-11-29,false,false,false,true
alertStateHistoryAnnotationsFromLoki,experimental,@grafana/alerting-squad,2023-11-30,false,false,true,false
lokiQueryHints,GA,@grafana/observability-logs,2023-12-18,false,false,false,true

1 Name Stage Owner Created requiresDevMode RequiresLicense RequiresRestart FrontendOnly
150 regressionTransformation privatePreview @grafana/grafana-bi-squad 2023-11-24 false false false true
151 displayAnonymousStats GA @grafana/identity-access-team 2023-11-29 false false false true
152 alertStateHistoryAnnotationsFromLoki experimental @grafana/alerting-squad 2023-11-30 false false true false
153 lokiQueryHints GA @grafana/observability-logs 2023-12-18 false false false true

View File

@ -610,4 +610,8 @@ const (
// FlagAlertStateHistoryAnnotationsFromLoki
// Enable using Loki as the source for panel annotations generated by alert rules
FlagAlertStateHistoryAnnotationsFromLoki = "alertStateHistoryAnnotationsFromLoki"
// FlagLokiQueryHints
// Enables query hints for Loki
FlagLokiQueryHints = "lokiQueryHints"
)

View File

@ -1,4 +1,5 @@
import { AbstractLabelOperator, DataFrame, TimeRange, dateTime, getDefaultTimeRange } from '@grafana/data';
import { config } from '@grafana/runtime';
import LanguageProvider from './LanguageProvider';
import { DEFAULT_MAX_LINES_SAMPLE, LokiDatasource } from './datasource';
@ -328,6 +329,14 @@ describe('Query imports', () => {
});
describe('getParserAndLabelKeys()', () => {
const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints;
beforeAll(() => {
config.featureToggles.lokiQueryHints = true;
});
afterAll(() => {
config.featureToggles.lokiQueryHints = queryHintsFeatureToggle;
});
let datasource: LokiDatasource, languageProvider: LanguageProvider;
const extractLogParserFromDataFrameMock = jest.mocked(extractLogParserFromDataFrame);
const extractedLabelKeys = ['extracted', 'label'];
@ -452,6 +461,12 @@ describe('Query imports', () => {
mockTimeRange
);
});
it('does not call dataSample with feature toggle disabled', async () => {
config.featureToggles.lokiQueryHints = false;
jest.spyOn(datasource, 'getDataSamples');
languageProvider.getParserAndLabelKeys('{place="luna"}', { timeRange: mockTimeRange });
expect(datasource.getDataSamples).not.toHaveBeenCalled();
});
});
});

View File

@ -2,6 +2,7 @@ import { LRUCache } from 'lru-cache';
import Prism from 'prismjs';
import { LanguageProvider, AbstractQuery, KeyValue, getDefaultTimeRange, TimeRange } from '@grafana/data';
import { config } from '@grafana/runtime';
import { extractLabelMatchers, processLabels, toPromLikeExpr } from 'app/plugins/datasource/prometheus/language_utils';
import { DEFAULT_MAX_LINES_SAMPLE, LokiDatasource } from './datasource';
@ -261,6 +262,18 @@ export default class LokiLanguageProvider extends LanguageProvider {
streamSelector: string,
options?: { maxLines?: number; timeRange?: TimeRange }
): Promise<ParserAndLabelKeysResult> {
const empty = {
extractedLabelKeys: [],
structuredMetadataKeys: [],
unwrapLabelKeys: [],
hasJSON: false,
hasLogfmt: false,
hasPack: false,
};
if (!config.featureToggles.lokiQueryHints) {
return empty;
}
const series = await this.datasource.getDataSamples(
{
expr: streamSelector,
@ -271,14 +284,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
);
if (!series.length) {
return {
extractedLabelKeys: [],
structuredMetadataKeys: [],
unwrapLabelKeys: [],
hasJSON: false,
hasLogfmt: false,
hasPack: false,
};
return empty;
}
const { hasLogfmt, hasJSON, hasPack } = extractLogParserFromDataFrame(series[0]);

View File

@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { DataSourceApi, getDefaultTimeRange, LoadingState, PanelData, SelectableValue, TimeRange } from '@grafana/data';
import { EditorRow } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox';
import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList';
@ -105,7 +106,9 @@ export const LokiQueryBuilder = React.memo<Props>(
setSampleData(sampleData);
};
onGetSampleData().catch(console.error);
if (config.featureToggles.lokiQueryHints) {
onGetSampleData().catch(console.error);
}
}, [datasource, query, timeRange]);
const lang = { grammar: logqlGrammar, name: 'logql' };

View File

@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
import React, { ComponentProps } from 'react';
import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
QueryBuilderOperation,
QueryBuilderOperationParamDef,
@ -15,6 +16,14 @@ import { LokiOperationId } from '../types';
import { UnwrapParamEditor } from './UnwrapParamEditor';
describe('UnwrapParamEditor', () => {
const queryHintsFeatureToggle = config.featureToggles.lokiQueryHints;
beforeAll(() => {
config.featureToggles.lokiQueryHints = true;
});
afterAll(() => {
config.featureToggles.lokiQueryHints = queryHintsFeatureToggle;
});
it('shows value if value present', () => {
const props = createProps({ value: 'unique' });
render(<UnwrapParamEditor {...props} />);
@ -53,6 +62,16 @@ describe('UnwrapParamEditor', () => {
expect(await screen.findByText('status')).toBeInTheDocument();
expect(await screen.findByText('duration')).toBeInTheDocument();
});
it('does not show labels with unwrap-friendly values when feature is disabled', async () => {
config.featureToggles.lokiQueryHints = false;
const props = createProps({}, frames);
render(<UnwrapParamEditor {...props} />);
const input = screen.getByRole('combobox');
await userEvent.click(input);
expect(screen.queryByText('status')).not.toBeInTheDocument();
expect(screen.queryByText('duration')).not.toBeInTheDocument();
});
});
const createProps = (

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Select } from '@grafana/ui';
import { QueryBuilderOperationParamEditorProps } from '../../../prometheus/querybuilder/shared/types';
@ -31,7 +32,7 @@ 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) {
if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) {
setState({ isLoading: true });
const options = await loadUnwrapOptions(query, datasource, timeRange);
setState({ options, isLoading: undefined });