Prometheus: Remove prometheusMetricEncyclopedia feature toggle (#98414)

* remove toggle from registry

* remove from metric combobox

* remove toggle from metric select

* remove toggle from promQueryBuilderContainer

* prettier
pull/98416/head
Brendan O'Handley 6 months ago committed by GitHub
parent d2639f6080
commit d935fa1ea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  2. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  3. 27
      packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.test.tsx
  4. 32
      packages/grafana-prometheus/src/querybuilder/components/MetricCombobox.tsx
  5. 18
      packages/grafana-prometheus/src/querybuilder/components/MetricSelect.test.tsx
  6. 83
      packages/grafana-prometheus/src/querybuilder/components/MetricSelect.tsx
  7. 5
      packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.test.tsx
  8. 32
      packages/grafana-prometheus/src/querybuilder/components/PromQueryBuilderContainer.tsx
  9. 9
      pkg/services/featuremgmt/registry.go
  10. 1
      pkg/services/featuremgmt/toggles_gen.csv
  11. 4
      pkg/services/featuremgmt/toggles_gen.go
  12. 1
      pkg/services/featuremgmt/toggles_gen.json

@ -32,7 +32,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `nestedFolders` | Enable folder nesting | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | 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 |
| `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 |

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

@ -4,7 +4,6 @@ import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { DataSourceInstanceSettings, MetricFindValue } from '@grafana/data';
import { config } from '@grafana/runtime';
import { PrometheusDatasource } from '../../datasource';
import { PromOptions } from '../../types';
@ -125,29 +124,17 @@ describe('MetricCombobox', () => {
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} />);
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', () => {
beforeAll(() => {
jest.replaceProperty(config, 'featureToggles', {
prometheusMetricEncyclopedia: true,
});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('opens the metrics explorer when the button is clicked', async () => {
render(<MetricCombobox {...defaultProps} onGetMetrics={() => Promise.resolve([])} />);
it('opens the metrics explorer when the button is clicked', async () => {
render(<MetricCombobox {...defaultProps} onGetMetrics={() => Promise.resolve([])} />);
const button = screen.getByRole('button', { name: /open metrics explorer/i });
await userEvent.click(button);
const button = screen.getByRole('button', { name: /open metrics explorer/i });
await userEvent.click(button);
expect(screen.getByText('Metrics explorer')).toBeInTheDocument();
});
expect(screen.getByText('Metrics explorer')).toBeInTheDocument();
});
});

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

@ -25,7 +25,6 @@ const instanceSettings = {
const dataSourceMock = new PrometheusDatasource(instanceSettings);
const mockValues = [{ label: 'random_metric' }, { label: 'unique_metric' }, { label: 'more_unique_metric' }];
// Mock metricFindQuery which will call backend API
//@ts-ignore
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('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 () => {
@ -81,7 +81,10 @@ describe('MetricSelect', () => {
render(<MetricSelect {...props} />);
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 () => {
@ -97,7 +100,8 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
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 () => {
@ -105,7 +109,8 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
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 () => {
@ -113,7 +118,8 @@ describe('MetricSelect', () => {
await openMetricSelect();
const input = screen.getByRole('combobox');
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 () => {

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

@ -19,9 +19,14 @@ describe('PromQueryBuilderContainer', () => {
expect(screen.getByText('metric_test')).toBeInTheDocument();
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({
disableTextWrap: false,
expr: 'rate(metric_test{job="testjob"}[$__rate_interval])',
fullMetaSearch: false,
includeNullMetadata: true,
refId: 'A',
useBackend: false,
});
});

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

@ -272,15 +272,6 @@ var (
Stage: FeatureStageExperimental,
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",
Description: "Query InfluxDB InfluxQL without the proxy",

@ -34,7 +34,6 @@ lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
lokiQuerySplittingConfig,experimental,@grafana/observability-logs,false,false,true
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
influxqlStreamingParser,experimental,@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

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

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

Loading…
Cancel
Save