Prometheus: Remove prometheusMetricEncyclopedia feature toggle (#98414)

* remove toggle from registry

* remove from metric combobox

* remove toggle from metric select

* remove toggle from promQueryBuilderContainer

* prettier
This commit is contained in:
Brendan O'Handley 2024-12-30 15:16:04 -06:00 committed by GitHub
parent d2639f6080
commit d935fa1ea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 77 additions and 137 deletions

View File

@ -32,7 +32,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `nestedFolders` | Enable folder nesting | Yes | | `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes | | `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes | | `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `prometheusMetricEncyclopedia` | Adds the metrics explorer component to the Prometheus query builder as an option in metric select | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes | | `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes | | `dataplaneFrontendFallback` | Support dataplane contract field name change for transformations and field name matchers where the name is different | Yes |
| `unifiedRequestLog` | Writes error logs to the request logger | Yes | | `unifiedRequestLog` | Writes error logs to the request logger | Yes |

View File

@ -53,7 +53,6 @@ export interface FeatureToggles {
lokiQuerySplitting?: boolean; lokiQuerySplitting?: boolean;
lokiQuerySplittingConfig?: boolean; lokiQuerySplittingConfig?: boolean;
individualCookiePreferences?: boolean; individualCookiePreferences?: boolean;
prometheusMetricEncyclopedia?: boolean;
influxdbBackendMigration?: boolean; influxdbBackendMigration?: boolean;
influxqlStreamingParser?: boolean; influxqlStreamingParser?: boolean;
influxdbRunQueriesInParallel?: boolean; influxdbRunQueriesInParallel?: boolean;

View File

@ -4,7 +4,6 @@ import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { DataSourceInstanceSettings, MetricFindValue } from '@grafana/data'; import { DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
import { config } from '@grafana/runtime';
import { PrometheusDatasource } from '../../datasource'; import { PrometheusDatasource } from '../../datasource';
import { PromOptions } from '../../types'; import { PromOptions } from '../../types';
@ -125,29 +124,17 @@ describe('MetricCombobox', () => {
expect(mockOnChange).toHaveBeenCalledWith({ metric: 'random_metric', labels: [], operations: [] }); expect(mockOnChange).toHaveBeenCalledWith({ metric: 'random_metric', labels: [], operations: [] });
}); });
it("doesn't show the metrics explorer button by default", () => { it('shows the metrics explorer button by default', () => {
render(<MetricCombobox {...defaultProps} />); render(<MetricCombobox {...defaultProps} />);
expect(screen.queryByRole('button', { name: /open metrics explorer/i })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: /open metrics explorer/i })).toBeInTheDocument();
}); });
describe('when metrics explorer toggle is enabled', () => { it('opens the metrics explorer when the button is clicked', async () => {
beforeAll(() => { render(<MetricCombobox {...defaultProps} onGetMetrics={() => Promise.resolve([])} />);
jest.replaceProperty(config, 'featureToggles', {
prometheusMetricEncyclopedia: true,
});
});
afterAll(() => { const button = screen.getByRole('button', { name: /open metrics explorer/i });
jest.restoreAllMocks(); await userEvent.click(button);
});
it('opens the metrics explorer when the button is clicked', async () => { expect(screen.getByText('Metrics explorer')).toBeInTheDocument();
render(<MetricCombobox {...defaultProps} onGetMetrics={() => Promise.resolve([])} />);
const button = screen.getByRole('button', { name: /open metrics explorer/i });
await userEvent.click(button);
expect(screen.getByText('Metrics explorer')).toBeInTheDocument();
});
}); });
}); });

View File

@ -3,7 +3,6 @@ import { useCallback, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { EditorField, EditorFieldGroup, InputGroup } from '@grafana/experimental'; import { EditorField, EditorFieldGroup, InputGroup } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { Button, ComponentSize, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui'; import { Button, ComponentSize, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui';
import { Combobox, ComboboxOption } from '@grafana/ui/src/components/Combobox/Combobox'; import { Combobox, ComboboxOption } from '@grafana/ui/src/components/Combobox/Combobox';
import { getPropertiesForButtonSize } from '@grafana/ui/src/components/Forms/commonStyles'; import { getPropertiesForButtonSize } from '@grafana/ui/src/components/Forms/commonStyles';
@ -89,8 +88,6 @@ export function MetricCombobox({
return metrics; return metrics;
}, [onGetMetrics]); }, [onGetMetrics]);
const metricsExplorerEnabled = config.featureToggles.prometheusMetricEncyclopedia;
const styles = useStyles2(getMectricComboboxStyles); const styles = useStyles2(getMectricComboboxStyles);
const asyncSelect = () => { const asyncSelect = () => {
@ -105,29 +102,24 @@ export function MetricCombobox({
onChange={onComboboxChange} onChange={onComboboxChange}
createCustomValue createCustomValue
/> />
<Button
{metricsExplorerEnabled ? ( size={BUTTON_SIZE}
<Button tooltip="Open metrics explorer"
size={BUTTON_SIZE} aria-label="Open metrics explorer"
tooltip="Open metrics explorer" variant="secondary"
aria-label="Open metrics explorer" icon="book-open"
variant="secondary" onClick={() => {
icon="book-open" tracking('grafana_prometheus_metric_encyclopedia_open', null, '', query);
onClick={() => { setMetricsModalOpen(true);
tracking('grafana_prometheus_metric_encyclopedia_open', null, '', query); }}
setMetricsModalOpen(true); />
}}
/>
) : (
<></>
)}
</InputGroup> </InputGroup>
); );
}; };
return ( return (
<> <>
{metricsExplorerEnabled && !datasource.lookupsDisabled && metricsModalOpen && ( {!datasource.lookupsDisabled && metricsModalOpen && (
<MetricsModal <MetricsModal
datasource={datasource} datasource={datasource}
isOpen={metricsModalOpen} isOpen={metricsModalOpen}

View File

@ -25,7 +25,6 @@ const instanceSettings = {
const dataSourceMock = new PrometheusDatasource(instanceSettings); const dataSourceMock = new PrometheusDatasource(instanceSettings);
const mockValues = [{ label: 'random_metric' }, { label: 'unique_metric' }, { label: 'more_unique_metric' }]; const mockValues = [{ label: 'random_metric' }, { label: 'unique_metric' }, { label: 'more_unique_metric' }];
// Mock metricFindQuery which will call backend API // Mock metricFindQuery which will call backend API
//@ts-ignore //@ts-ignore
dataSourceMock.metricFindQuery = jest.fn((query: string) => { dataSourceMock.metricFindQuery = jest.fn((query: string) => {
@ -69,7 +68,8 @@ describe('MetricSelect', () => {
await waitFor(() => expect(screen.getByText('random_metric')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('random_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('unique_metric')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('unique_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getByText('more_unique_metric')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('more_unique_metric')).toBeInTheDocument());
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3)); await waitFor(() => expect(screen.getByText('Metrics explorer')).toBeInTheDocument());
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(4));
}); });
it('truncates list of metrics to 1000', async () => { it('truncates list of metrics to 1000', async () => {
@ -81,7 +81,10 @@ describe('MetricSelect', () => {
render(<MetricSelect {...props} />); render(<MetricSelect {...props} />);
await openMetricSelect(); await openMetricSelect();
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(1000)); // the metrics explorer is added as a custom option
const optionsLength = screen.getAllByTestId(selectors.components.Select.option).length;
const optionsLengthMinusMetricsExplorer = optionsLength - 1;
await waitFor(() => expect(optionsLengthMinusMetricsExplorer).toBe(1000));
}); });
it('shows option to set custom value when typing', async () => { it('shows option to set custom value when typing', async () => {
@ -97,7 +100,8 @@ describe('MetricSelect', () => {
await openMetricSelect(); await openMetricSelect();
const input = screen.getByRole('combobox'); const input = screen.getByRole('combobox');
await userEvent.type(input, 'unique'); await userEvent.type(input, 'unique');
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3)); const optionsLength = mockValues.length + 1; // the metrics explorer is added as a custom option
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(optionsLength));
}); });
it('searches on split words', async () => { it('searches on split words', async () => {
@ -105,7 +109,8 @@ describe('MetricSelect', () => {
await openMetricSelect(); await openMetricSelect();
const input = screen.getByRole('combobox'); const input = screen.getByRole('combobox');
await userEvent.type(input, 'more unique'); await userEvent.type(input, 'more unique');
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(2)); // the metrics explorer is added as a custom option
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3));
}); });
it('searches on multiple split words', async () => { it('searches on multiple split words', async () => {
@ -113,7 +118,8 @@ describe('MetricSelect', () => {
await openMetricSelect(); await openMetricSelect();
const input = screen.getByRole('combobox'); const input = screen.getByRole('combobox');
await userEvent.type(input, 'more unique metric'); await userEvent.type(input, 'more unique metric');
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(2)); // the metrics explorer is added as a custom option
await waitFor(() => expect(screen.getAllByTestId(selectors.components.Select.option)).toHaveLength(3));
}); });
it('highlights matching string', async () => { it('highlights matching string', async () => {

View File

@ -8,7 +8,6 @@ import Highlighter from 'react-highlight-words';
import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { EditorField, EditorFieldGroup } from '@grafana/experimental'; import { EditorField, EditorFieldGroup } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { import {
AsyncSelect, AsyncSelect,
Button, Button,
@ -67,8 +66,6 @@ export function MetricSelect({
resultsTruncated?: boolean; resultsTruncated?: boolean;
}>({}); }>({});
const prometheusMetricEncyclopedia = config.featureToggles.prometheusMetricEncyclopedia;
const metricsModalOption: SelectableValue[] = [ const metricsModalOption: SelectableValue[] = [
{ {
value: 'BrowseMetrics', value: 'BrowseMetrics',
@ -77,33 +74,25 @@ export function MetricSelect({
}, },
]; ];
const customFilterOption = useCallback( const customFilterOption = useCallback((option: SelectableValue, searchQuery: string) => {
(option: SelectableValue, searchQuery: string) => { const label = option.label ?? option.value;
const label = option.label ?? option.value; if (!label) {
if (!label) { return false;
return false; }
}
// custom value is not a string label but a react node // custom value is not a string label but a react node
if (!label.toLowerCase) { if (!label.toLowerCase) {
return true; return true;
} }
const searchWords = searchQuery.split(splitSeparator); const searchWords = searchQuery.split(splitSeparator);
return searchWords.reduce((acc, cur) => { return searchWords.reduce((acc, cur) => {
const matcheSearch = label.toLowerCase().includes(cur.toLowerCase()); const matcheSearch = label.toLowerCase().includes(cur.toLowerCase());
const browseOption = label === 'Metrics explorer';
let browseOption = false; return acc && (matcheSearch || browseOption);
if (prometheusMetricEncyclopedia) { }, true);
browseOption = label === 'Metrics explorer'; }, []);
}
return acc && (matcheSearch || browseOption);
}, true);
},
[prometheusMetricEncyclopedia]
);
const formatOptionLabel = useCallback( const formatOptionLabel = useCallback(
(option: SelectableValue, meta: FormatOptionLabelMeta<any>) => { (option: SelectableValue, meta: FormatOptionLabelMeta<any>) => {
@ -159,11 +148,7 @@ export function MetricSelect({
}; };
}); });
if (prometheusMetricEncyclopedia) { return [...metricsModalOption, ...resultsOptions];
return [...metricsModalOption, ...resultsOptions];
} else {
return resultsOptions;
}
}); });
}; };
@ -286,22 +271,14 @@ export function MetricSelect({
truncateResult(metrics); truncateResult(metrics);
} }
if (prometheusMetricEncyclopedia) { setState({
setState({ // add the modal button option to the options
// add the modal button option to the options metrics: [...metricsModalOption, ...metrics],
metrics: [...metricsModalOption, ...metrics], isLoading: undefined,
isLoading: undefined, // pass the initial metrics into the metrics explorer
// pass the initial metrics into the metrics explorer initialMetrics: initialMetrics,
initialMetrics: initialMetrics, resultsTruncated: resultsLength > metrics.length,
resultsTruncated: resultsLength > metrics.length, });
});
} else {
setState({
metrics,
isLoading: undefined,
resultsTruncated: resultsLength > metrics.length,
});
}
}} }}
loadOptions={metricLookupDisabled ? metricLookupDisabledSearch : debouncedSearch} loadOptions={metricLookupDisabled ? metricLookupDisabledSearch : debouncedSearch}
isLoading={state.isLoading} isLoading={state.isLoading}
@ -309,8 +286,8 @@ export function MetricSelect({
onChange={(input) => { onChange={(input) => {
const value = input?.value; const value = input?.value;
if (value) { if (value) {
// if there is no metric and the m.e. is enabled, open the modal // if there is no metric and the value is the custom m.e. option, open the modal
if (prometheusMetricEncyclopedia && value === 'BrowseMetrics') { if (value === 'BrowseMetrics') {
tracking('grafana_prometheus_metric_encyclopedia_open', null, '', query); tracking('grafana_prometheus_metric_encyclopedia_open', null, '', query);
setState({ ...state, metricsModalOpen: true }); setState({ ...state, metricsModalOpen: true });
} else { } else {
@ -320,9 +297,7 @@ export function MetricSelect({
onChange({ ...query, metric: '' }); onChange({ ...query, metric: '' });
} }
}} }}
components={ components={{ Option: CustomOption, MenuList: CustomMenu }}
prometheusMetricEncyclopedia ? { Option: CustomOption, MenuList: CustomMenu } : { MenuList: CustomMenu }
}
onBlur={onBlur} onBlur={onBlur}
/> />
); );
@ -330,7 +305,7 @@ export function MetricSelect({
return ( return (
<> <>
{prometheusMetricEncyclopedia && !datasource.lookupsDisabled && state.metricsModalOpen && ( {!datasource.lookupsDisabled && state.metricsModalOpen && (
<MetricsModal <MetricsModal
datasource={datasource} datasource={datasource}
isOpen={state.metricsModalOpen} isOpen={state.metricsModalOpen}

View File

@ -19,9 +19,14 @@ describe('PromQueryBuilderContainer', () => {
expect(screen.getByText('metric_test')).toBeInTheDocument(); expect(screen.getByText('metric_test')).toBeInTheDocument();
await addOperationInQueryBuilder('Range functions', 'Rate'); await addOperationInQueryBuilder('Range functions', 'Rate');
// extra fields here are for storing metrics explorer settings. Future work: store these in local storage.
expect(props.onChange).toHaveBeenCalledWith({ expect(props.onChange).toHaveBeenCalledWith({
disableTextWrap: false,
expr: 'rate(metric_test{job="testjob"}[$__rate_interval])', expr: 'rate(metric_test{job="testjob"}[$__rate_interval])',
fullMetaSearch: false,
includeNullMetadata: true,
refId: 'A', refId: 'A',
useBackend: false,
}); });
}); });

View File

@ -3,7 +3,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { useEffect, useReducer } from 'react'; import { useEffect, useReducer } from 'react';
import { PanelData } from '@grafana/data'; import { PanelData } from '@grafana/data';
import { config } from '@grafana/runtime';
import { PrometheusDatasource } from '../../datasource'; import { PrometheusDatasource } from '../../datasource';
import { PromQuery } from '../../types'; import { PromQuery } from '../../types';
@ -29,8 +28,6 @@ export interface State {
expr: string; expr: string;
} }
const prometheusMetricEncyclopedia = config.featureToggles.prometheusMetricEncyclopedia;
/** /**
* This component is here just to contain the translation logic between string query and the visual query builder model. * This component is here just to contain the translation logic between string query and the visual query builder model.
*/ */
@ -40,17 +37,14 @@ export function PromQueryBuilderContainer(props: PromQueryBuilderContainerProps)
// Only rebuild visual query if expr changes from outside // Only rebuild visual query if expr changes from outside
useEffect(() => { useEffect(() => {
dispatch(exprChanged(query.expr)); dispatch(exprChanged(query.expr));
dispatch(
if (prometheusMetricEncyclopedia) { setMetricsModalSettings({
dispatch( useBackend: query.useBackend ?? false,
setMetricsModalSettings({ disableTextWrap: query.disableTextWrap ?? false,
useBackend: query.useBackend ?? false, fullMetaSearch: query.fullMetaSearch ?? false,
disableTextWrap: query.disableTextWrap ?? false, includeNullMetadata: query.includeNullMetadata ?? true,
fullMetaSearch: query.fullMetaSearch ?? false, })
includeNullMetadata: query.includeNullMetadata ?? true, );
})
);
}
}, [query]); }, [query]);
useEffect(() => { useEffect(() => {
@ -61,12 +55,8 @@ export function PromQueryBuilderContainer(props: PromQueryBuilderContainerProps)
const expr = promQueryModeller.renderQuery(visQuery); const expr = promQueryModeller.renderQuery(visQuery);
dispatch(visualQueryChange({ visQuery, expr })); dispatch(visualQueryChange({ visQuery, expr }));
if (prometheusMetricEncyclopedia) { const metricsModalSettings = getSettings(visQuery);
const metricsModalSettings = getSettings(visQuery); onChange({ ...props.query, expr: expr, ...metricsModalSettings });
onChange({ ...props.query, expr: expr, ...metricsModalSettings });
} else {
onChange({ ...props.query, expr: expr });
}
}; };
if (!state.visQuery) { if (!state.visQuery) {
@ -109,7 +99,7 @@ const stateSlice = createSlice({
} }
}, },
setMetricsModalSettings: (state, action: PayloadAction<MetricsModalSettings>) => { setMetricsModalSettings: (state, action: PayloadAction<MetricsModalSettings>) => {
if (state.visQuery && prometheusMetricEncyclopedia) { if (state.visQuery) {
state.visQuery.useBackend = action.payload.useBackend; state.visQuery.useBackend = action.payload.useBackend;
state.visQuery.disableTextWrap = action.payload.disableTextWrap; state.visQuery.disableTextWrap = action.payload.disableTextWrap;
state.visQuery.fullMetaSearch = action.payload.fullMetaSearch; state.visQuery.fullMetaSearch = action.payload.fullMetaSearch;

View File

@ -272,15 +272,6 @@ var (
Stage: FeatureStageExperimental, Stage: FeatureStageExperimental,
Owner: grafanaBackendGroup, Owner: grafanaBackendGroup,
}, },
{
Name: "prometheusMetricEncyclopedia",
Description: "Adds the metrics explorer component to the Prometheus query builder as an option in metric select",
Expression: "true",
Stage: FeatureStageGeneralAvailability,
FrontendOnly: true,
Owner: grafanaObservabilityMetricsSquad,
AllowSelfServe: true,
},
{ {
Name: "influxdbBackendMigration", Name: "influxdbBackendMigration",
Description: "Query InfluxDB InfluxQL without the proxy", Description: "Query InfluxDB InfluxQL without the proxy",

View File

@ -34,7 +34,6 @@ lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
lokiQuerySplittingConfig,experimental,@grafana/observability-logs,false,false,true lokiQuerySplittingConfig,experimental,@grafana/observability-logs,false,false,true
individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false
prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,false,false,true
influxdbBackendMigration,GA,@grafana/observability-metrics,false,false,true influxdbBackendMigration,GA,@grafana/observability-metrics,false,false,true
influxqlStreamingParser,experimental,@grafana/observability-metrics,false,false,false influxqlStreamingParser,experimental,@grafana/observability-metrics,false,false,false
influxdbRunQueriesInParallel,privatePreview,@grafana/observability-metrics,false,false,false influxdbRunQueriesInParallel,privatePreview,@grafana/observability-metrics,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
34 lokiQuerySplitting GA @grafana/observability-logs false false true
35 lokiQuerySplittingConfig experimental @grafana/observability-logs false false true
36 individualCookiePreferences experimental @grafana/grafana-backend-group false false false
prometheusMetricEncyclopedia GA @grafana/observability-metrics false false true
37 influxdbBackendMigration GA @grafana/observability-metrics false false true
38 influxqlStreamingParser experimental @grafana/observability-metrics false false false
39 influxdbRunQueriesInParallel privatePreview @grafana/observability-metrics false false false

View File

@ -147,10 +147,6 @@ const (
// Support overriding cookie preferences per user // Support overriding cookie preferences per user
FlagIndividualCookiePreferences = "individualCookiePreferences" FlagIndividualCookiePreferences = "individualCookiePreferences"
// FlagPrometheusMetricEncyclopedia
// Adds the metrics explorer component to the Prometheus query builder as an option in metric select
FlagPrometheusMetricEncyclopedia = "prometheusMetricEncyclopedia"
// FlagInfluxdbBackendMigration // FlagInfluxdbBackendMigration
// Query InfluxDB InfluxQL without the proxy // Query InfluxDB InfluxQL without the proxy
FlagInfluxdbBackendMigration = "influxdbBackendMigration" FlagInfluxdbBackendMigration = "influxdbBackendMigration"

View File

@ -2952,6 +2952,7 @@
"name": "prometheusMetricEncyclopedia", "name": "prometheusMetricEncyclopedia",
"resourceVersion": "1720021873452", "resourceVersion": "1720021873452",
"creationTimestamp": "2023-03-07T18:41:05Z", "creationTimestamp": "2023-03-07T18:41:05Z",
"deletionTimestamp": "2024-12-30T14:42:45Z",
"annotations": { "annotations": {
"grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC"
} }