mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Add lokiQueryHints
feature flag (#78953)
* add `lokiQueryHints` feature flag * language provider tests * fix unwrap tests * add feature toggle
This commit is contained in:
parent
456939bac4
commit
2165c9b3f0
@ -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
|
||||
|
||||
|
@ -169,4 +169,5 @@ export interface FeatureToggles {
|
||||
regressionTransformation?: boolean;
|
||||
displayAnonymousStats?: boolean;
|
||||
alertStateHistoryAnnotationsFromLoki?: boolean;
|
||||
lokiQueryHints?: boolean;
|
||||
}
|
||||
|
@ -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),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -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
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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' };
|
||||
|
@ -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 = (
|
||||
|
@ -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 });
|
||||
|
Loading…
Reference in New Issue
Block a user