Remove PromExploreQueryEditor and PromQueryEditor (#60020)

Remove Prom editor
This commit is contained in:
Ludovic Viaud 2022-12-09 12:56:47 +01:00 committed by GitHub
parent cf342f1933
commit 07d6b632b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 16 additions and 525 deletions

View File

@ -6306,22 +6306,12 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx:5381": [ "public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/plugins/datasource/prometheus/components/PromLink.test.tsx:5381": [ "public/app/plugins/datasource/prometheus/components/PromLink.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/datasource/prometheus/components/PromLink.tsx:5381": [ "public/app/plugins/datasource/prometheus/components/PromLink.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/prometheus/components/PromQueryEditor.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx:5381": [ "public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],

View File

@ -1,144 +0,0 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { LoadingState, PanelData, toUtc, TimeRange } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromQuery } from '../types';
import { testIds as extraFieldTestIds } from './PromExploreExtraField';
import { PromExploreQueryEditor, testIds } from './PromExploreQueryEditor';
// the monaco-based editor uses lazy-loading and that does not work
// well with this test, and we do not need the monaco-related
// functionality in this test anyway, so we mock it out.
jest.mock('./monaco-query-field/MonacoQueryFieldWrapper', () => {
const fakeQueryField = () => <div>prometheus query field</div>;
return {
MonacoQueryFieldWrapper: fakeQueryField,
};
});
const setup = (propOverrides?: object) => {
const datasourceMock: unknown = {
languageProvider: {
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
start: () => Promise.resolve([]),
},
getInitHints: () => [],
exemplarsAvailable: true,
};
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource;
const onRunQuery = jest.fn();
const onChange = jest.fn();
const query: PromQuery = { expr: '', refId: 'A', interval: '1s', exemplar: true };
const range: TimeRange = {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
},
};
const data: PanelData = {
state: LoadingState.NotStarted,
series: [],
request: {
requestId: '1',
dashboardId: 1,
intervalMs: 1000,
interval: '1s',
panelId: 1,
range: {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
},
},
scopedVars: {},
targets: [],
timezone: 'GMT',
app: 'Grafana',
startTime: 0,
},
timeRange: {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
raw: {
from: toUtc('2020-01-01', 'YYYY-MM-DD'),
to: toUtc('2020-01-02', 'YYYY-MM-DD'),
},
},
};
const history: any[] = [];
const exploreMode = 'Metrics';
const props: any = {
query,
data,
range,
datasource,
exploreMode,
history,
onChange,
onRunQuery,
};
Object.assign(props, propOverrides);
return <PromExploreQueryEditor {...props} />;
};
describe('PromExploreQueryEditor', () => {
it('should render component', () => {
render(setup());
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
});
it('should render PromQueryField with ExtraFieldElement', async () => {
render(setup());
expect(screen.getByTestId(extraFieldTestIds.extraFieldEditor)).toBeInTheDocument();
});
it('should set default value for expr if it is undefined', async () => {
const onChange = jest.fn();
const query = { expr: undefined, exemplar: false, instant: false, range: true };
render(setup({ onChange, query }));
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ expr: '' }));
});
it('should set default value for exemplars if it is undefined', async () => {
const onChange = jest.fn();
const query = { expr: '', instant: false, range: true };
render(setup({ onChange, query }));
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ exemplar: true }));
});
it('should set default value for instant and range if expr is falsy', async () => {
const onChange = jest.fn();
let query = { expr: '', exemplar: true };
render(setup({ onChange, query }));
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ instant: true, range: true }));
});
it('should not set default value for instant and range with truthy expr', async () => {
const onChange = jest.fn();
let query = { expr: 'foo', exemplar: true };
render(setup({ onChange, query }));
expect(onChange).toHaveBeenCalledTimes(0);
});
it('should add default values for multiple missing values', async () => {
const onChange = jest.fn();
let query = {};
render(setup({ onChange, query }));
expect(onChange).toHaveBeenCalledTimes(3);
});
});

View File

@ -1,54 +0,0 @@
import React, { memo, useEffect } from 'react';
import { QueryEditorProps, CoreApp } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromQuery, PromOptions } from '../types';
import { PromExploreExtraField } from './PromExploreExtraField';
import PromQueryField from './PromQueryField';
export type Props = QueryEditorProps<PrometheusDatasource, PromQuery, PromOptions>;
export const PromExploreQueryEditor = memo((props: Props) => {
const { range, query, data, datasource, history, onChange, onRunQuery } = props;
// Setting default values
useEffect(() => {
if (query.expr === undefined) {
onChange({ ...query, expr: '' });
}
if (query.exemplar === undefined) {
onChange({ ...query, exemplar: true });
}
// Override query type to "Both" only for new queries (no query.expr).
if (!query.instant && !query.range && !query.expr) {
onChange({ ...query, instant: true, range: true });
}
}, [onChange, query]);
return (
<PromQueryField
app={CoreApp.Explore}
datasource={datasource}
query={query}
range={range}
onRunQuery={onRunQuery}
onChange={onChange}
onBlur={() => {}}
history={history}
data={data}
data-testid={testIds.editor}
ExtraFieldElement={
<PromExploreExtraField query={query} onChange={onChange} datasource={datasource} onRunQuery={onRunQuery} />
}
/>
);
});
PromExploreQueryEditor.displayName = 'PromExploreQueryEditor';
export const testIds = {
editor: 'prom-editor-explore',
};

View File

@ -1,75 +0,0 @@
import { screen, render } from '@testing-library/react';
import React from 'react';
import { dateTime, CoreApp } from '@grafana/data';
import { PrometheusDatasource } from '../datasource';
import { PromQuery } from '../types';
import { PromQueryEditor, testIds } from './PromQueryEditor';
jest.mock('app/features/dashboard/services/TimeSrv', () => {
return {
getTimeSrv: () => ({
timeRange: () => ({
from: dateTime(),
to: dateTime(),
}),
}),
};
});
jest.mock('./monaco-query-field/MonacoQueryFieldWrapper', () => {
const fakeQueryField = () => <div>prometheus query field</div>;
return {
MonacoQueryFieldWrapper: fakeQueryField,
};
});
const setup = (propOverrides?: object) => {
const datasourceMock: unknown = {
createQuery: jest.fn((q) => q),
getPrometheusTime: jest.fn((date, roundup) => 123),
languageProvider: {
start: () => Promise.resolve([]),
syntax: () => {},
getLabelKeys: () => [],
metrics: [],
},
getInitHints: () => [],
};
const datasource: PrometheusDatasource = datasourceMock as PrometheusDatasource;
const onRunQuery = jest.fn();
const onChange = jest.fn();
const query: PromQuery = { expr: '', refId: 'A' };
const props: any = {
datasource,
onChange,
onRunQuery,
query,
};
Object.assign(props, propOverrides);
return render(<PromQueryEditor {...props} />);
};
describe('Render PromQueryEditor with basic options', () => {
it('should render editor', () => {
setup();
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
});
it('should render exemplar editor for dashboard', () => {
setup({ app: CoreApp.Dashboard });
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
expect(screen.getByTestId(testIds.exemplar)).toBeInTheDocument();
});
it('should not render exemplar editor for unified alerting', () => {
setup({ app: CoreApp.UnifiedAlerting });
expect(screen.getByTestId(testIds.editor)).toBeInTheDocument();
expect(screen.queryByTestId(testIds.exemplar)).not.toBeInTheDocument();
});
});

View File

@ -1,223 +0,0 @@
import { map } from 'lodash';
import React, { PureComponent } from 'react';
// Types
import { CoreApp, SelectableValue } from '@grafana/data';
import { InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
import { PromQuery } from '../types';
import { PromExemplarField } from './PromExemplarField';
import PromLink from './PromLink';
import PromQueryField from './PromQueryField';
import { PromQueryEditorProps } from './types';
const { Switch } = LegacyForms;
export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' },
{ label: 'Heatmap', value: 'heatmap' },
];
export const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({
value,
label: '1/' + value,
}));
interface State {
legendFormat?: string;
formatOption: SelectableValue<string>;
interval?: string;
intervalFactorOption: SelectableValue<number>;
instant: boolean;
exemplar: boolean;
}
export class PromQueryEditor extends PureComponent<PromQueryEditorProps, State> {
// Query target to be modified and used for queries
query: PromQuery;
constructor(props: PromQueryEditorProps) {
super(props);
// Use default query to prevent undefined input values
const defaultQuery: Partial<PromQuery> = {
expr: '',
legendFormat: '',
interval: '',
// Set exemplar to false for alerting queries
exemplar: props.app === CoreApp.UnifiedAlerting ? false : true,
};
const query = Object.assign({}, defaultQuery, props.query);
this.query = query;
// Query target properties that are fully controlled inputs
this.state = {
// Fully controlled text inputs
interval: query.interval,
legendFormat: query.legendFormat,
// Select options
formatOption: FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0],
intervalFactorOption:
INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor) || INTERVAL_FACTOR_OPTIONS[0],
// Switch options
instant: Boolean(query.instant),
exemplar: Boolean(query.exemplar),
};
}
onFieldChange = (query: PromQuery, override?: any) => {
this.query.expr = query.expr;
};
onFormatChange = (option: SelectableValue<string>) => {
this.query.format = option.value;
this.setState({ formatOption: option }, this.onRunQuery);
};
onInstantChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const instant = e.currentTarget.checked;
this.query.instant = instant;
this.setState({ instant }, this.onRunQuery);
};
onIntervalChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const interval = e.currentTarget.value;
this.query.interval = interval;
this.setState({ interval });
};
onIntervalFactorChange = (option: SelectableValue<number>) => {
this.query.intervalFactor = option.value;
this.setState({ intervalFactorOption: option }, this.onRunQuery);
};
onLegendChange = (e: React.SyntheticEvent<HTMLInputElement>) => {
const legendFormat = e.currentTarget.value;
this.query.legendFormat = legendFormat;
this.setState({ legendFormat });
};
onExemplarChange = (isEnabled: boolean) => {
this.query.exemplar = isEnabled;
this.setState({ exemplar: isEnabled }, this.onRunQuery);
};
onRunQuery = () => {
const { query } = this;
// Change of query.hide happens outside of this component and is just passed as prop. We have to update it when running queries.
const { hide } = this.props.query;
this.props.onChange({ ...query, hide });
this.props.onRunQuery();
};
render() {
const { datasource, query, range, data } = this.props;
const { formatOption, instant, interval, intervalFactorOption, legendFormat } = this.state;
//We want to hide exemplar field for unified alerting as exemplars in alerting don't make sense and are source of confusion
const showExemplarField = this.props.app !== CoreApp.UnifiedAlerting;
return (
<PromQueryField
datasource={datasource}
query={query}
range={range}
onRunQuery={this.onRunQuery}
onChange={this.onFieldChange}
history={[]}
data={data}
data-testid={testIds.editor}
ExtraFieldElement={
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel
width={7}
tooltip="Controls the name of the time series, using name or pattern. For example
{{hostname}} will be replaced with label value for the label hostname."
>
Legend
</InlineFormLabel>
<input
type="text"
className="gf-form-input"
placeholder="legend format"
value={legendFormat}
onChange={this.onLegendChange}
onBlur={this.onRunQuery}
/>
</div>
<div className="gf-form">
<InlineFormLabel
width={7}
tooltip={
<>
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
<code>$__interval</code> and <code>$__rate_interval</code> variables. The limit is absolute and not
modified by the &quot;Resolution&quot; setting.
</>
}
>
Min step
</InlineFormLabel>
<input
type="text"
className="gf-form-input width-8"
aria-label="Set lower limit for the step parameter"
placeholder={interval}
onChange={this.onIntervalChange}
onBlur={this.onRunQuery}
value={interval}
/>
</div>
<div className="gf-form">
<div className="gf-form-label">Resolution</div>
<Select
aria-label="Select resolution"
isSearchable={false}
options={INTERVAL_FACTOR_OPTIONS}
onChange={this.onIntervalFactorChange}
value={intervalFactorOption}
/>
</div>
<div className="gf-form">
<div className="gf-form-label width-7">Format</div>
<Select
className="select-container"
width={16}
isSearchable={false}
options={FORMAT_OPTIONS}
onChange={this.onFormatChange}
value={formatOption}
aria-label="Select format"
/>
<Switch label="Instant" checked={instant} onChange={this.onInstantChange} />
<InlineFormLabel width={10} tooltip="Link to Graph in Prometheus">
<PromLink
datasource={datasource}
query={this.query} // Use modified query
panelData={data}
/>
</InlineFormLabel>
</div>
{showExemplarField && (
<PromExemplarField
onChange={this.onExemplarChange}
datasource={datasource}
query={this.query}
data-testid={testIds.exemplar}
/>
)}
</div>
}
/>
);
}
}
export const testIds = {
editor: 'prom-editor',
exemplar: 'exemplar-editor',
};

View File

@ -6,7 +6,6 @@ import { CoreApp } from '@grafana/data';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
import { testIds as regularTestIds } from './PromQueryEditor';
import { PromQueryEditorByApp } from './PromQueryEditorByApp'; import { PromQueryEditorByApp } from './PromQueryEditorByApp';
import { testIds as alertingTestIds } from './PromQueryEditorForAlerting'; import { testIds as alertingTestIds } from './PromQueryEditorForAlerting';
@ -70,10 +69,9 @@ function setup(app: CoreApp): RenderResult & { onRunQuery: jest.Mock } {
describe('PromQueryEditorByApp', () => { describe('PromQueryEditorByApp', () => {
it('should render simplified query editor for cloud alerting', () => { it('should render simplified query editor for cloud alerting', () => {
const { getByTestId, queryByTestId } = setup(CoreApp.CloudAlerting); const { getByTestId } = setup(CoreApp.CloudAlerting);
expect(getByTestId(alertingTestIds.editor)).toBeInTheDocument(); expect(getByTestId(alertingTestIds.editor)).toBeInTheDocument();
expect(queryByTestId(regularTestIds.editor)).toBeNull();
}); });
it('should render editor selector for unkown apps', () => { it('should render editor selector for unkown apps', () => {

View File

@ -1,12 +1,9 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { CoreApp } from '@grafana/data'; import { CoreApp } from '@grafana/data';
import { config } from '@grafana/runtime';
import { PromQueryEditorSelector } from '../querybuilder/components/PromQueryEditorSelector'; import { PromQueryEditorSelector } from '../querybuilder/components/PromQueryEditorSelector';
import { PromExploreQueryEditor } from './PromExploreQueryEditor';
import { PromQueryEditor } from './PromQueryEditor';
import { PromQueryEditorForAlerting } from './PromQueryEditorForAlerting'; import { PromQueryEditorForAlerting } from './PromQueryEditorForAlerting';
import { PromQueryEditorProps } from './types'; import { PromQueryEditorProps } from './types';
@ -16,16 +13,8 @@ export function PromQueryEditorByApp(props: PromQueryEditorProps) {
switch (app) { switch (app) {
case CoreApp.CloudAlerting: case CoreApp.CloudAlerting:
return <PromQueryEditorForAlerting {...props} />; return <PromQueryEditorForAlerting {...props} />;
case CoreApp.Explore:
if (config.featureToggles.promQueryBuilder) {
return <PromQueryEditorSelector {...props} />;
}
return <PromExploreQueryEditor {...props} />;
default: default:
if (config.featureToggles.promQueryBuilder) { return <PromQueryEditorSelector {...props} />;
return <PromQueryEditorSelector {...props} />;
}
return <PromQueryEditor {...props} />;
} }
} }

View File

@ -5,10 +5,10 @@ import { EditorField, EditorRow, EditorSwitch } from '@grafana/experimental';
import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui'; import { AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField'; import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField';
import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from '../../components/PromQueryEditor';
import { PromQuery } from '../../types'; import { PromQuery } from '../../types';
import { QueryOptionGroup } from '../shared/QueryOptionGroup'; import { QueryOptionGroup } from '../shared/QueryOptionGroup';
import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from './PromQueryEditorSelector';
import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor'; import { getLegendModeLabel, PromQueryLegendEditor } from './PromQueryLegendEditor';
export interface UIOptions { export interface UIOptions {

View File

@ -4,7 +4,6 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui';
import { testIds } from '../../components/PromQueryEditor';
import PromQueryField from '../../components/PromQueryField'; import PromQueryField from '../../components/PromQueryField';
import { PromQueryEditorProps } from '../../components/types'; import { PromQueryEditorProps } from '../../components/types';
@ -28,7 +27,6 @@ export function PromQueryCodeEditor(props: Props) {
onChange={onChange} onChange={onChange}
history={[]} history={[]}
data={data} data={data}
data-testid={testIds.editor}
app={app} app={app}
/> />

View File

@ -1,6 +1,7 @@
import { map } from 'lodash';
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
import { CoreApp, LoadingState } from '@grafana/data'; import { CoreApp, LoadingState, SelectableValue } from '@grafana/data';
import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental'; import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { ConfirmModal, Button } from '@grafana/ui'; import { ConfirmModal, Button } from '@grafana/ui';
@ -19,6 +20,17 @@ import { PromQueryBuilderContainer } from './PromQueryBuilderContainer';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions'; import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { PromQueryCodeEditor } from './PromQueryCodeEditor'; import { PromQueryCodeEditor } from './PromQueryCodeEditor';
export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' },
{ label: 'Heatmap', value: 'heatmap' },
];
export const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({
value,
label: '1/' + value,
}));
type Props = PromQueryEditorProps; type Props = PromQueryEditorProps;
export const PromQueryEditorSelector = React.memo<Props>((props) => { export const PromQueryEditorSelector = React.memo<Props>((props) => {