Prometheus: Specific code mode view that has no options, instead sharing options with builder (#45260)

* Prometheus: Adding a slimmed down code mode for new prometheus query ux

* Show Both option in Explore

* Prometheus: Adding query defaults handling

* More tweaks

* Fixing defaults and logic for when to show exemplars toggle

* Fixing tooltip text

* Set exemplars to false when setting query type instant

* Updated test
This commit is contained in:
Torkel Ödegaard 2022-02-11 14:43:52 +01:00 committed by GitHub
parent 59c5f14a59
commit aa6cee1072
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 43 deletions

View File

@ -124,7 +124,7 @@ export function getQueryTypeOptions(includeBoth: boolean) {
export function getQueryTypeChangeHandler(query: PromQuery, onChange: (update: PromQuery) => void) {
return (queryType: string) => {
if (queryType === 'instant') {
onChange({ ...query, instant: true, range: false });
onChange({ ...query, instant: true, range: false, exemplar: false });
} else if (queryType === 'range') {
onChange({ ...query, instant: false, range: true });
} else {

View File

@ -19,7 +19,7 @@ export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Heatmap', value: 'heatmap' },
];
const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({
export const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = map([1, 2, 3, 4, 5, 10], (value: number) => ({
value,
label: '1/' + value,
}));

View File

@ -6,7 +6,6 @@ import { PromQuery } from '../../types';
import { buildVisualQueryFromString } from '../parsing';
import { promQueryModeller } from '../PromQueryModeller';
import { PromQueryBuilder } from './PromQueryBuilder';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { QueryPreview } from './QueryPreview';
import { PromVisualQuery } from '../types';
@ -24,7 +23,7 @@ export interface Props {
* @constructor
*/
export function PromQueryBuilderContainer(props: Props) {
const { query, onChange, onRunQuery, datasource, app } = props;
const { query, onChange, onRunQuery, datasource } = props;
const visQuery = buildVisualQueryFromString(query.expr || '').query;
@ -37,7 +36,6 @@ export function PromQueryBuilderContainer(props: Props) {
<>
<PromQueryBuilder query={visQuery} datasource={datasource} onChange={onVisQueryChange} onRunQuery={onRunQuery} />
{query.editorPreview && <QueryPreview query={query.expr} />}
<PromQueryBuilderOptions query={query} app={app} onChange={onChange} onRunQuery={onRunQuery} />
</>
);
}

View File

@ -4,7 +4,7 @@ import { CoreApp, SelectableValue } from '@grafana/data';
import { Input, RadioButtonGroup, Select, Switch } from '@grafana/ui';
import { QueryOptionGroup } from '../shared/QueryOptionGroup';
import { PromQuery } from '../../types';
import { FORMAT_OPTIONS } from '../../components/PromQueryEditor';
import { FORMAT_OPTIONS, INTERVAL_FACTOR_OPTIONS } from '../../components/PromQueryEditor';
import { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField';
export interface Props {
@ -32,7 +32,7 @@ export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange
onRunQuery();
};
const queryTypeOptions = getQueryTypeOptions(false);
const queryTypeOptions = getQueryTypeOptions(app === CoreApp.Explore);
const onQueryTypeChange = getQueryTypeChangeHandler(query, onChange);
const onExemplarChange = (event: SyntheticEvent<HTMLInputElement>) => {
@ -41,15 +41,17 @@ export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange
onRunQuery();
};
const showExemplarSwitch = app !== CoreApp.UnifiedAlerting && !query.instant;
const onIntervalFactorChange = (value: SelectableValue<number>) => {
onChange({ ...query, intervalFactor: value.value });
onRunQuery();
};
return (
<EditorRow>
<QueryOptionGroup title="Options" collapsedInfo={getCollapsedInfo(query, formatOption)}>
<EditorField
label="Legend"
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."
tooltip="Series name override or template. Ex. {{hostname}} will be replaced with label value for hostname."
>
<Input placeholder="auto" defaultValue={query.legendFormat} onBlur={onLegendFormatChanged} />
</EditorField>
@ -76,22 +78,42 @@ export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange
<Select value={formatOption} allowCustomValue onChange={onChangeFormat} options={FORMAT_OPTIONS} />
</EditorField>
<EditorField label="Type">
<RadioButtonGroup
options={queryTypeOptions}
value={query.range && query.instant ? 'both' : query.instant ? 'instant' : 'range'}
onChange={onQueryTypeChange}
/>
<RadioButtonGroup options={queryTypeOptions} value={getQueryTypeValue(query)} onChange={onQueryTypeChange} />
</EditorField>
{showExemplarSwitch && (
{shouldShowExemplarSwitch(query, app) && (
<EditorField label="Exemplars">
<Switch value={query.exemplar} onChange={onExemplarChange} />
</EditorField>
)}
{query.intervalFactor && query.intervalFactor > 1 && (
<EditorField label="Resolution">
<Select
aria-label="Select resolution"
menuShouldPortal
isSearchable={false}
options={INTERVAL_FACTOR_OPTIONS}
onChange={onIntervalFactorChange}
value={INTERVAL_FACTOR_OPTIONS.find((option) => option.value === query.intervalFactor)}
/>
</EditorField>
)}
</QueryOptionGroup>
</EditorRow>
);
});
function shouldShowExemplarSwitch(query: PromQuery, app?: CoreApp) {
if (app === CoreApp.UnifiedAlerting || !query.range) {
return false;
}
return true;
}
function getQueryTypeValue(query: PromQuery) {
return query.range && query.instant ? 'both' : query.instant ? 'instant' : 'range';
}
function getCollapsedInfo(query: PromQuery, formatOption: SelectableValue<string>): string[] {
const items: string[] = [];
@ -105,9 +127,7 @@ function getCollapsedInfo(query: PromQuery, formatOption: SelectableValue<string
items.push(`Step ${query.interval}`);
}
if (query.instant) {
items.push(`Instant: true`);
}
items.push(`Type: ${getQueryTypeValue(query)}`);
if (query.exemplar) {
items.push(`Exemplars: true`);

View File

@ -0,0 +1,19 @@
import React from 'react';
import { PromQueryEditorProps } from '../../components/types';
import PromQueryField from '../../components/PromQueryField';
import { testIds } from '../../components/PromQueryEditor';
export function PromQueryCodeEditor({ query, datasource, range, onRunQuery, onChange, data }: PromQueryEditorProps) {
return (
<PromQueryField
datasource={datasource}
query={query}
range={range}
onRunQuery={onRunQuery}
onChange={onChange}
history={[]}
data={data}
data-testid={testIds.editor}
/>
);
}

View File

@ -71,7 +71,6 @@ describe('PromQueryEditorSelector', () => {
it('shows builder when builder mode is set', async () => {
renderWithMode(QueryEditorMode.Builder);
screen.debug(undefined, 20000);
expectBuilder();
});
@ -86,6 +85,8 @@ describe('PromQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
instant: false,
range: true,
editorMode: QueryEditorMode.Builder,
});
});
@ -99,6 +100,8 @@ describe('PromQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
instant: false,
range: true,
editorMode: QueryEditorMode.Builder,
editorPreview: true,
});
@ -119,6 +122,8 @@ describe('PromQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
instant: false,
range: true,
editorMode: QueryEditorMode.Code,
});
});
@ -129,6 +134,8 @@ describe('PromQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
instant: false,
range: true,
editorMode: QueryEditorMode.Explain,
});
});

View File

@ -1,10 +1,8 @@
import React, { SyntheticEvent, useCallback, useState } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental';
import { Button, ConfirmModal, useStyles2 } from '@grafana/ui';
import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
import { PromQueryEditor } from '../../components/PromQueryEditor';
import { PromQueryEditorProps } from '../../components/types';
import { promQueryModeller } from '../PromQueryModeller';
import { QueryEditorModeToggle } from '../shared/QueryEditorModeToggle';
@ -12,13 +10,17 @@ import { QueryHeaderSwitch } from '../shared/QueryHeaderSwitch';
import { QueryEditorMode } from '../shared/types';
import { PromQueryBuilderExplained } from './PromQueryBuilderExplained';
import { buildVisualQueryFromString } from '../parsing';
import { PromQueryCodeEditor } from './PromQueryCodeEditor';
import { PromQueryBuilderContainer } from './PromQueryBuilderContainer';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { getQueryWithDefaults } from '../types';
export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props) => {
const { query, onChange, onRunQuery, data } = props;
const { onChange, onRunQuery, data } = props;
const styles = useStyles2(getStyles);
const [parseModalOpen, setParseModalOpen] = useState(false);
const query = getQueryWithDefaults(props.query, props.app);
const editorMode = query.editorMode!;
const onEditorModeChange = useCallback(
(newMetricEditorMode: QueryEditorMode) => {
@ -42,15 +44,6 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
onRunQuery();
};
// If no expr (ie new query) then default to builder
const editorMode = query.editorMode ?? (query.expr ? QueryEditorMode.Code : QueryEditorMode.Builder);
useEffect(() => {
if (query.editorMode === undefined) {
onChange({ ...query, editorMode });
}
}, [editorMode, onChange, query]);
return (
<>
<ConfirmModal
@ -107,7 +100,7 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
</EditorHeader>
<Space v={0.5} />
<EditorRows>
{editorMode === QueryEditorMode.Code && <PromQueryEditor {...props} />}
{editorMode === QueryEditorMode.Code && <PromQueryCodeEditor {...props} />}
{editorMode === QueryEditorMode.Builder && (
<PromQueryBuilderContainer
query={query}
@ -117,6 +110,9 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
/>
)}
{editorMode === QueryEditorMode.Explain && <PromQueryBuilderExplained query={query.expr} />}
{editorMode !== QueryEditorMode.Explain && (
<PromQueryBuilderOptions query={query} app={props.app} onChange={onChange} onRunQuery={onRunQuery} />
)}
</EditorRows>
</>
);

View File

@ -1,5 +1,7 @@
import { CoreApp } from '@grafana/data';
import { PromQuery } from '../types';
import { VisualQueryBinary } from './shared/LokiAndPromQueryModellerBase';
import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types';
import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryEditorMode } from './shared/types';
/**
* Visual query model
@ -54,12 +56,35 @@ export interface PromQueryPattern {
operations: QueryBuilderOperation[];
}
export function getDefaultEmptyQuery() {
const model: PromVisualQuery = {
metric: '',
labels: [],
operations: [],
};
/**
* Returns query with defaults, and boolean true/false depending on change was required
*/
export function getQueryWithDefaults(query: PromQuery, app: CoreApp | undefined): PromQuery {
// If no expr (ie new query) then default to builder
let result = query;
const editorMode = query.editorMode ?? (query.expr ? QueryEditorMode.Code : QueryEditorMode.Builder);
return model;
if (result.editorMode !== editorMode) {
result = { ...result, editorMode };
}
if (query.expr == null) {
result = { ...result, expr: '' };
}
// Default to range query
if (query.range == null) {
result = { ...result, range: true };
}
// In explore we default to both instant & range
if (query.instant == null && query.range == null) {
if (app === CoreApp.Explore) {
result = { ...result, instant: true };
} else {
result = { ...result, instant: false, range: true };
}
}
return result;
}