Prometheus: Adds missing options to the new query builder (#44915)

* Prometheus: Query builder legend format, first working state

* Added format option

* Working options section

* Wrapping stuff up removing old stuff

* Fixed ts issues

* Review fixes

* Added unit tests for preview toggle
This commit is contained in:
Torkel Ödegaard 2022-02-07 13:32:36 +01:00 committed by GitHub
parent d9d1f8520e
commit f92ab9bc72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 352 additions and 105 deletions

View File

@ -16,16 +16,7 @@ export interface PromExploreExtraFieldProps {
export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
({ query, datasource, onChange, onRunQuery }) => {
const rangeOptions = [
{ value: 'range', label: 'Range', description: 'Run query over a range of time.' },
{
value: 'instant',
label: 'Instant',
description: 'Run query against a single point in time. For this query, the "To" time is used.',
},
{ value: 'both', label: 'Both', description: 'Run an Instant query and a Range query.' },
];
const rangeOptions = getQueryTypeOptions(true);
const prevQuery = usePrevious(query);
const onExemplarChange = useCallback(
@ -53,17 +44,7 @@ export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
}
}
function onQueryTypeChange(queryType: string) {
let nextQuery;
if (queryType === 'instant') {
nextQuery = { ...query, instant: true, range: false };
} else if (queryType === 'range') {
nextQuery = { ...query, instant: false, range: true };
} else {
nextQuery = { ...query, instant: true, range: true };
}
onChange(nextQuery);
}
const onQueryTypeChange = getQueryTypeChangeHandler(query, onChange);
return (
<div aria-label="Prometheus extra field" className="gf-form-inline" data-testid={testIds.extraFieldEditor}>
@ -123,6 +104,35 @@ export const PromExploreExtraField: React.FC<PromExploreExtraFieldProps> = memo(
PromExploreExtraField.displayName = 'PromExploreExtraField';
export function getQueryTypeOptions(includeBoth: boolean) {
const rangeOptions = [
{ value: 'range', label: 'Range', description: 'Run query over a range of time' },
{
value: 'instant',
label: 'Instant',
description: 'Run query against a single point in time. For this query, the "To" time is used',
},
];
if (includeBoth) {
rangeOptions.push({ value: 'both', label: 'Both', description: 'Run an Instant query and a Range query' });
}
return rangeOptions;
}
export function getQueryTypeChangeHandler(query: PromQuery, onChange: (update: PromQuery) => void) {
return (queryType: string) => {
if (queryType === 'instant') {
onChange({ ...query, instant: true, range: false });
} else if (queryType === 'range') {
onChange({ ...query, instant: false, range: true });
} else {
onChange({ ...query, instant: true, range: true });
}
};
}
export const testIds = {
extraFieldEditor: 'prom-editor-extra-field',
stepField: 'prom-editor-extra-field-step',

View File

@ -13,7 +13,7 @@ import { PromQueryEditorProps } from './types';
const { Switch } = LegacyForms;
const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
export const FORMAT_OPTIONS: Array<SelectableValue<string>> = [
{ label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' },
{ label: 'Heatmap', value: 'heatmap' },

View File

@ -1,4 +1,5 @@
import { PromQueryModeller } from './PromQueryModeller';
import { PromOperationId } from './types';
describe('PromQueryModeller', () => {
const modeller = new PromQueryModeller();
@ -41,7 +42,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [],
operations: [{ id: 'histogram_quantile', params: [0.86] }],
operations: [{ id: PromOperationId.HistogramQuantile, params: [0.86] }],
})
).toBe('histogram_quantile(0.86, metric)');
});
@ -51,7 +52,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [],
operations: [{ id: 'label_replace', params: ['server', '$1', 'instance', 'as(.*)d'] }],
operations: [{ id: PromOperationId.LabelReplace, params: ['server', '$1', 'instance', 'as(.*)d'] }],
})
).toBe('label_replace(metric, "server", "$1", "instance", "as(.*)d")');
});
@ -94,7 +95,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [{ label: 'pod', op: '=', value: 'A' }],
operations: [{ id: 'rate', params: ['auto'] }],
operations: [{ id: PromOperationId.Rate, params: ['auto'] }],
})
).toBe('rate(metric{pod="A"}[$__rate_interval])');
});
@ -104,7 +105,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [{ label: 'pod', op: '=', value: 'A' }],
operations: [{ id: 'increase', params: ['auto'] }],
operations: [{ id: PromOperationId.Increase, params: ['auto'] }],
})
).toBe('increase(metric{pod="A"}[$__rate_interval])');
});
@ -114,7 +115,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [{ label: 'pod', op: '=', value: 'A' }],
operations: [{ id: 'rate', params: ['10m'] }],
operations: [{ id: PromOperationId.Rate, params: ['10m'] }],
})
).toBe('rate(metric{pod="A"}[10m])');
});
@ -124,7 +125,7 @@ describe('PromQueryModeller', () => {
modeller.renderQuery({
metric: 'metric',
labels: [],
operations: [{ id: '__multiply_by', params: [1000] }],
operations: [{ id: PromOperationId.MultiplyBy, params: [1000] }],
})
).toBe('metric * 1000');
});

View File

@ -47,8 +47,6 @@ const bugQuery: PromVisualQuery = {
describe('PromQueryBuilder', () => {
it('shows empty just with metric selected', async () => {
setup();
// One should be select another query preview
expect(screen.getAllByText('random_metric').length).toBe(2);
// Add label
expect(screen.getByLabelText('Add')).toBeInTheDocument();
expect(screen.getByLabelText('Add operation')).toBeInTheDocument();
@ -67,9 +65,6 @@ describe('PromQueryBuilder', () => {
expect(screen.getByText('Binary operations')).toBeInTheDocument();
expect(screen.getByText('Operator')).toBeInTheDocument();
expect(screen.getByText('Vector matches')).toBeInTheDocument();
expect(screen.getByLabelText('selector').textContent).toBe(
'sum by(instance, job) (rate(random_metric{instance="localhost:9090"}[$__rate_interval])) / sum by(app) (metric2{foo="bar"})'
);
});
it('tries to load metrics without labels', async () => {

View File

@ -3,12 +3,11 @@ import { MetricSelect } from './MetricSelect';
import { PromVisualQuery } from '../types';
import { LabelFilters } from '../shared/LabelFilters';
import { OperationList } from '../shared/OperationList';
import { EditorRows, EditorRow } from '@grafana/experimental';
import { EditorRow } from '@grafana/experimental';
import { PrometheusDatasource } from '../../datasource';
import { NestedQueryList } from './NestedQueryList';
import { promQueryModeller } from '../PromQueryModeller';
import { QueryBuilderLabelFilter } from '../shared/types';
import { QueryPreview } from './QueryPreview';
import { DataSourceApi, SelectableValue } from '@grafana/data';
import { OperationsEditorRow } from '../shared/OperationsEditorRow';
@ -20,7 +19,7 @@ export interface Props {
nested?: boolean;
}
export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange, onRunQuery, nested }) => {
export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange, onRunQuery }) => {
const onChangeLabels = (labels: QueryBuilderLabelFilter[]) => {
onChange({ ...query, labels });
};
@ -78,7 +77,7 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
};
return (
<EditorRows>
<>
<EditorRow>
<MetricSelect
query={query}
@ -108,12 +107,7 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
)}
</OperationsEditorRow>
{!nested && (
<EditorRow>
<QueryPreview query={query} />
</EditorRow>
)}
</EditorRows>
</>
);
});

View File

@ -0,0 +1,119 @@
import React, { SyntheticEvent } from 'react';
import { EditorRow, EditorField } from '@grafana/experimental';
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 { getQueryTypeChangeHandler, getQueryTypeOptions } from '../../components/PromExploreExtraField';
export interface Props {
query: PromQuery;
app?: CoreApp;
onChange: (update: PromQuery) => void;
onRunQuery: () => void;
}
export const PromQueryBuilderOptions = React.memo<Props>(({ query, app, onChange, onRunQuery }) => {
const formatOption = FORMAT_OPTIONS.find((option) => option.value === query.format) || FORMAT_OPTIONS[0];
const onChangeFormat = (value: SelectableValue<string>) => {
onChange({ ...query, format: value.value });
onRunQuery();
};
const onLegendFormatChanged = (evt: React.FocusEvent<HTMLInputElement>) => {
onChange({ ...query, legendFormat: evt.currentTarget.value });
onRunQuery();
};
const onChangeStep = (evt: React.FocusEvent<HTMLInputElement>) => {
onChange({ ...query, interval: evt.currentTarget.value });
onRunQuery();
};
const queryTypeOptions = getQueryTypeOptions(false);
const onQueryTypeChange = getQueryTypeChangeHandler(query, onChange);
const onExemplarChange = (event: SyntheticEvent<HTMLInputElement>) => {
const isEnabled = event.currentTarget.checked;
onChange({ ...query, exemplar: isEnabled });
onRunQuery();
};
const showExemplarSwitch = app !== CoreApp.UnifiedAlerting && !query.instant;
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."
>
<Input placeholder="auto" defaultValue={query.legendFormat} onBlur={onLegendFormatChanged} />
</EditorField>
<EditorField
label="Min step"
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.
</>
}
>
<Input
type="text"
aria-label="Set lower limit for the step parameter"
placeholder={'auto'}
width={10}
onBlur={onChangeStep}
defaultValue={query.interval}
/>
</EditorField>
<EditorField label="Format">
<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}
/>
</EditorField>
{showExemplarSwitch && (
<EditorField label="Exemplars">
<Switch value={query.exemplar} onChange={onExemplarChange} />
</EditorField>
)}
</QueryOptionGroup>
</EditorRow>
);
});
function getCollapsedInfo(query: PromQuery, formatOption: SelectableValue<string>): string[] {
const items: string[] = [];
if (query.legendFormat) {
items.push(`Legend: ${query.legendFormat}`);
}
items.push(`Format: ${formatOption.label}`);
if (query.interval) {
items.push(`Step ${query.interval}`);
}
if (query.instant) {
items.push(`Instant: true`);
}
if (query.exemplar) {
items.push(`Exemplars: true`);
}
return items;
}
PromQueryBuilderOptions.displayName = 'PromQueryBuilderOptions';

View File

@ -6,6 +6,8 @@ import { PrometheusDatasource } from '../../datasource';
import { QueryEditorMode } from '../shared/types';
import { EmptyLanguageProviderMock } from '../../language_provider.mock';
import PromQlLanguageProvider from '../../language_provider';
import { cloneDeep, defaultsDeep } from 'lodash';
import { PromQuery } from '../../types';
// We need to mock this because it seems jest has problem importing monaco in tests
jest.mock('../../components/monaco-query-field/MonacoQueryFieldWrapper', () => {
@ -82,17 +84,44 @@ describe('PromQueryEditorSelector', () => {
switchToMode(QueryEditorMode.Builder);
expect(onChange).toBeCalledWith({
refId: 'A',
expr: '',
expr: defaultQuery.expr,
editorMode: QueryEditorMode.Builder,
});
});
it('Can enable preview', async () => {
const { onChange } = renderWithMode(QueryEditorMode.Builder);
expect(screen.queryByLabelText('selector')).not.toBeInTheDocument();
screen.getByLabelText('Preview').click();
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
editorMode: QueryEditorMode.Builder,
editorPreview: true,
});
});
it('Should show preview', async () => {
renderWithProps({
editorPreview: true,
editorMode: QueryEditorMode.Builder,
visualQuery: {
metric: 'my_metric',
labels: [],
operations: [],
},
});
expect(screen.getByLabelText('selector').textContent).toBe('my_metric');
});
it('changes to code mode', async () => {
const { onChange } = renderWithMode(QueryEditorMode.Builder);
switchToMode(QueryEditorMode.Code);
expect(onChange).toBeCalledWith({
refId: 'A',
expr: '',
expr: defaultQuery.expr,
editorMode: QueryEditorMode.Code,
});
});
@ -102,25 +131,21 @@ describe('PromQueryEditorSelector', () => {
switchToMode(QueryEditorMode.Explain);
expect(onChange).toBeCalledWith({
refId: 'A',
expr: '',
expr: defaultQuery.expr,
editorMode: QueryEditorMode.Explain,
});
});
});
function renderWithMode(mode: QueryEditorMode) {
return renderWithProps({ editorMode: mode } as any);
}
function renderWithProps(overrides?: Partial<PromQuery>) {
const query = defaultsDeep(overrides ?? {}, cloneDeep(defaultQuery));
const onChange = jest.fn();
render(
<PromQueryEditorSelector
{...defaultProps}
onChange={onChange}
query={{
refId: 'A',
expr: '',
editorMode: mode,
}}
/>
);
render(<PromQueryEditorSelector {...defaultProps} query={query} onChange={onChange} />);
return { onChange };
}

View File

@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { CoreApp, GrafanaTheme2, LoadingState } from '@grafana/data';
import { EditorHeader, FlexItem, InlineSelect, Space } from '@grafana/experimental';
import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental';
import { Button, useStyles2 } from '@grafana/ui';
import React, { SyntheticEvent, useCallback, useState } from 'react';
import { PromQueryEditor } from '../../components/PromQueryEditor';
@ -12,6 +12,8 @@ import { QueryEditorMode } from '../shared/types';
import { getDefaultEmptyQuery, PromVisualQuery } from '../types';
import { PromQueryBuilder } from './PromQueryBuilder';
import { PromQueryBuilderExplained } from './PromQueryBuilderExplained';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { QueryPreview } from './QueryPreview';
export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props) => {
const { query, onChange, onRunQuery, data } = props;
@ -36,21 +38,14 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
});
};
const onInstantChange = (event: SyntheticEvent<HTMLInputElement>) => {
const onQueryPreviewChange = (event: SyntheticEvent<HTMLInputElement>) => {
const isEnabled = event.currentTarget.checked;
onChange({ ...query, instant: isEnabled, exemplar: false });
onRunQuery();
};
const onExemplarChange = (event: SyntheticEvent<HTMLInputElement>) => {
const isEnabled = event.currentTarget.checked;
onChange({ ...query, exemplar: isEnabled });
onChange({ ...query, editorPreview: isEnabled });
onRunQuery();
};
// If no expr (ie new query) then default to builder
const editorMode = query.editorMode ?? (query.expr ? QueryEditorMode.Code : QueryEditorMode.Builder);
const showExemplarSwitch = props.app !== CoreApp.UnifiedAlerting && !query.instant;
return (
<>
@ -67,10 +62,6 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
>
Run query
</Button>
<QueryHeaderSwitch label="Instant" value={query.instant} onChange={onInstantChange} />
{showExemplarSwitch && (
<QueryHeaderSwitch label="Exemplars" value={query.exemplar} onChange={onExemplarChange} />
)}
{editorMode === QueryEditorMode.Builder && (
<>
<InlineSelect
@ -87,19 +78,31 @@ export const PromQueryEditorSelector = React.memo<PromQueryEditorProps>((props)
/>
</>
)}
<QueryHeaderSwitch
label="Preview"
value={query.editorPreview}
onChange={onQueryPreviewChange}
disabled={editorMode !== QueryEditorMode.Builder}
/>
<QueryEditorModeToggle mode={editorMode} onChange={onEditorModeChange} />
</EditorHeader>
<Space v={0.5} />
{editorMode === QueryEditorMode.Code && <PromQueryEditor {...props} />}
{editorMode === QueryEditorMode.Builder && (
<PromQueryBuilder
query={visualQuery}
datasource={props.datasource}
onChange={onChangeViewModel}
onRunQuery={props.onRunQuery}
/>
)}
{editorMode === QueryEditorMode.Explain && <PromQueryBuilderExplained query={visualQuery} />}
<EditorRows>
{editorMode === QueryEditorMode.Code && <PromQueryEditor {...props} />}
{editorMode === QueryEditorMode.Builder && (
<>
<PromQueryBuilder
query={visualQuery}
datasource={props.datasource}
onChange={onChangeViewModel}
onRunQuery={props.onRunQuery}
/>
{query.editorPreview && <QueryPreview query={visualQuery} />}
<PromQueryBuilderOptions query={query} app={props.app} onChange={onChange} onRunQuery={onRunQuery} />
</>
)}
{editorMode === QueryEditorMode.Explain && <PromQueryBuilderExplained query={visualQuery} />}
</EditorRows>
</>
);
});

View File

@ -4,7 +4,7 @@ import { useTheme2 } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { promQueryModeller } from '../PromQueryModeller';
import { css, cx } from '@emotion/css';
import { EditorField, EditorFieldGroup } from '@grafana/experimental';
import { EditorField, EditorFieldGroup, EditorRow } from '@grafana/experimental';
import Prism from 'prismjs';
import { promqlGrammar } from '../../promql';
@ -18,22 +18,23 @@ export function QueryPreview({ query }: Props) {
const hightlighted = Prism.highlight(promQueryModeller.renderQuery(query), promqlGrammar, 'promql');
return (
<EditorFieldGroup>
<EditorField label="Query text">
<div
className={cx(styles.editorField, 'prism-syntax-highlight')}
aria-label="selector"
dangerouslySetInnerHTML={{ __html: hightlighted }}
/>
</EditorField>
</EditorFieldGroup>
<EditorRow>
<EditorFieldGroup>
<EditorField label="Preview">
<div
className={cx(styles.editorField, 'prism-syntax-highlight')}
aria-label="selector"
dangerouslySetInnerHTML={{ __html: hightlighted }}
/>
</EditorField>
</EditorFieldGroup>
</EditorRow>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
editorField: css({
padding: theme.spacing(0.25, 1),
fontFamily: theme.typography.fontFamilyMonospace,
fontSize: theme.typography.bodySmall.fontSize,
}),

View File

@ -10,12 +10,12 @@ import {
QueryBuilderOperationParamDef,
VisualQueryModeller,
} from './shared/types';
import { PromVisualQuery, PromVisualQueryOperationCategory } from './types';
import { PromOperationId, PromVisualQuery, PromVisualQueryOperationCategory } from './types';
export function getOperationDefinitions(): QueryBuilderOperationDef[] {
const list: QueryBuilderOperationDef[] = [
{
id: 'histogram_quantile',
id: PromOperationId.HistogramQuantile,
name: 'Histogram quantile',
params: [{ name: 'Quantile', type: 'number', options: [0.99, 0.95, 0.9, 0.75, 0.5, 0.25] }],
defaultParams: [0.9],
@ -24,7 +24,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: defaultAddOperationHandler,
},
{
id: 'label_replace',
id: PromOperationId.LabelReplace,
name: 'Label replace',
params: [
{ name: 'Destination label', type: 'string' },
@ -38,7 +38,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: defaultAddOperationHandler,
},
{
id: 'ln',
id: PromOperationId.Ln,
name: 'Ln',
params: [],
defaultParams: [],
@ -46,15 +46,15 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
renderer: functionRendererLeft,
addOperationHandler: defaultAddOperationHandler,
},
createRangeFunction('changes'),
createRangeFunction('rate'),
createRangeFunction('irate'),
createRangeFunction('increase'),
createRangeFunction('delta'),
createRangeFunction(PromOperationId.Changes),
createRangeFunction(PromOperationId.Rate),
createRangeFunction(PromOperationId.Irate),
createRangeFunction(PromOperationId.Increase),
createRangeFunction(PromOperationId.Delta),
// Not sure about this one. It could also be a more generic "Simple math operation" where user specifies
// both the operator and the operand in a single input
{
id: '__multiply_by',
id: PromOperationId.MultiplyBy,
name: 'Multiply by scalar',
params: [{ name: 'Factor', type: 'number' }],
defaultParams: [2],
@ -63,7 +63,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: defaultAddOperationHandler,
},
{
id: '__divide_by',
id: PromOperationId.DivideBy,
name: 'Divide by scalar',
params: [{ name: 'Factor', type: 'number' }],
defaultParams: [2],
@ -72,7 +72,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: defaultAddOperationHandler,
},
{
id: '__nested_query',
id: PromOperationId.NestedQuery,
name: 'Binary operation with query',
params: [],
defaultParams: [],

View File

@ -0,0 +1,82 @@
import React from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { Icon, useStyles2 } from '@grafana/ui';
import { useToggle } from 'react-use';
export interface Props {
title: string;
collapsedInfo: string[];
children: React.ReactNode;
}
export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {
const [isOpen, toggleOpen] = useToggle(false);
const styles = useStyles2(getStyles);
return (
<Stack gap={0} direction="column">
<div className={styles.header} onClick={toggleOpen} title="Click to edit options">
<div className={styles.toggle}>
<Icon name={isOpen ? 'angle-down' : 'angle-right'} />
</div>
<h6 className={styles.title}>{title}</h6>
{!isOpen && (
<div className={styles.description}>
{collapsedInfo.map((x, i) => (
<span key={i}>{x}</span>
))}
</div>
)}
</div>
{isOpen && <div className={styles.body}>{children}</div>}
</Stack>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
switchLabel: css({
color: theme.colors.text.secondary,
cursor: 'pointer',
fontSize: theme.typography.bodySmall.fontSize,
'&:hover': {
color: theme.colors.text.primary,
},
}),
header: css({
display: 'flex',
cursor: 'pointer',
alignItems: 'baseline',
color: theme.colors.text.primary,
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
},
}),
title: css({
flexGrow: 1,
overflow: 'hidden',
fontSize: theme.typography.bodySmall.fontSize,
fontWeight: theme.typography.fontWeightMedium,
margin: 0,
}),
description: css({
color: theme.colors.text.secondary,
fontSize: theme.typography.bodySmall.fontSize,
paddingLeft: theme.spacing(2),
gap: theme.spacing(2),
display: 'flex',
}),
body: css({
display: 'flex',
paddingTop: theme.spacing(2),
gap: theme.spacing(2),
flexWrap: 'wrap',
}),
toggle: css({
color: theme.colors.text.secondary,
marginRight: `${theme.spacing(1)}`,
}),
};
};

View File

@ -20,6 +20,20 @@ export enum PromVisualQueryOperationCategory {
BinaryOps = 'Binary operations',
}
export enum PromOperationId {
HistogramQuantile = 'histogram_quantile',
LabelReplace = 'label_replace',
Ln = 'ln',
Changes = 'changes',
Rate = 'rate',
Irate = 'irate',
Increase = 'increase',
Delta = 'delta',
MultiplyBy = '__multiply_by',
DivideBy = '__divide_by',
NestedQuery = '__nested_query',
}
export interface PromQueryPattern {
name: string;
operations: QueryBuilderOperation[];

View File

@ -18,7 +18,10 @@ export interface PromQuery extends DataQuery {
requestId?: string;
showingGraph?: boolean;
showingTable?: boolean;
/** Code, Builder or Explain */
editorMode?: QueryEditorMode;
/** Controls if the query preview is shown */
editorPreview?: boolean;
/** Temporary until we have a parser */
visualQuery?: PromVisualQuery;
}