Prometheus: Improvements to binary operations, nesting and parantheses handling (#45384)

* Prometheus: Improve query nesting ux

* Prometheus: Add parentheses around nested queries with binary ops

* removed unnessary typing change

* Fixing ts issues

* Improved paranthesis logic

* Fixing unit test

* Progress
This commit is contained in:
Torkel Ödegaard 2022-02-15 21:05:35 +01:00 committed by GitHub
parent 46360ca0c3
commit 52ae586452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 124 additions and 67 deletions

View File

@ -188,6 +188,76 @@ describe('PromQueryModeller', () => {
).toBe('metric_a + metric_b + metric_c');
});
it('Can render query with nested query with binary op', () => {
expect(
modeller.renderQuery({
metric: 'metric_a',
labels: [],
operations: [],
binaryQueries: [
{
operator: '/',
query: {
metric: 'metric_b',
labels: [],
operations: [{ id: PromOperationId.MultiplyBy, params: [1000] }],
},
},
],
})
).toBe('metric_a / (metric_b * 1000)');
});
it('Can render query with nested binary query with parentheses', () => {
expect(
modeller.renderQuery({
metric: 'metric_a',
labels: [],
operations: [],
binaryQueries: [
{
operator: '/',
query: {
metric: 'metric_b',
labels: [],
operations: [],
binaryQueries: [
{
operator: '*',
query: {
metric: 'metric_c',
labels: [],
operations: [],
},
},
],
},
},
],
})
).toBe('metric_a / (metric_b * metric_c)');
});
it('Should add parantheis around first query if it has binary op', () => {
expect(
modeller.renderQuery({
metric: 'metric_a',
labels: [],
operations: [{ id: PromOperationId.MultiplyBy, params: [1000] }],
binaryQueries: [
{
operator: '/',
query: {
metric: 'metric_b',
labels: [],
operations: [],
},
},
],
})
).toBe('(metric_a * 1000) / metric_b');
});
it('Can render with binary queries with vectorMatches expression', () => {
expect(
modeller.renderQuery({

View File

@ -25,13 +25,32 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase<PromVisualQu
]);
}
renderQuery(query: PromVisualQuery) {
renderQuery(query: PromVisualQuery, nested?: boolean) {
let queryString = `${query.metric}${this.renderLabels(query.labels)}`;
queryString = this.renderOperations(queryString, query.operations);
if (!nested && this.hasBinaryOp(query) && Boolean(query.binaryQueries?.length)) {
queryString = `(${queryString})`;
}
queryString = this.renderBinaryQueries(queryString, query.binaryQueries);
if (nested && (this.hasBinaryOp(query) || Boolean(query.binaryQueries?.length))) {
queryString = `(${queryString})`;
}
return queryString;
}
hasBinaryOp(query: PromVisualQuery): boolean {
return (
query.operations.find((op) => {
const def = this.getOperationDef(op.id);
return def.category === PromVisualQueryOperationCategory.BinaryOps;
}) !== undefined
);
}
getQueryPatterns(): PromQueryPattern[] {
return [
{

View File

@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, toOption } from '@grafana/data';
import { FlexItem } from '@grafana/experimental';
import { EditorRows, FlexItem } from '@grafana/experimental';
import { IconButton, Input, Select, useStyles2 } from '@grafana/ui';
import React from 'react';
import { PrometheusDatasource } from '../../datasource';
@ -51,15 +51,17 @@ export const NestedQuery = React.memo<Props>(({ nestedQuery, index, datasource,
<IconButton name="times" size="sm" onClick={() => onRemove(index)} />
</div>
<div className={styles.body}>
<PromQueryBuilder
query={nestedQuery.query}
datasource={datasource}
nested={true}
onRunQuery={onRunQuery}
onChange={(update) => {
onChange(index, { ...nestedQuery, query: update });
}}
/>
<EditorRows>
<PromQueryBuilder
query={nestedQuery.query}
datasource={datasource}
nested={true}
onRunQuery={onRunQuery}
onChange={(update) => {
onChange(index, { ...nestedQuery, query: update });
}}
/>
</EditorRows>
</div>
</div>
);
@ -79,15 +81,11 @@ NestedQuery.displayName = 'NestedQuery';
const getStyles = (theme: GrafanaTheme2) => {
return {
card: css({
background: theme.colors.background.primary,
border: `1px solid ${theme.colors.border.medium}`,
display: 'flex',
flexDirection: 'column',
cursor: 'grab',
borderRadius: theme.shape.borderRadius(1),
gap: theme.spacing(0.5),
}),
header: css({
borderBottom: `1px solid ${theme.colors.border.medium}`,
padding: theme.spacing(0.5, 0.5, 0.5, 1),
gap: theme.spacing(1),
display: 'flex',
@ -97,8 +95,7 @@ const getStyles = (theme: GrafanaTheme2) => {
whiteSpace: 'nowrap',
}),
body: css({
margin: theme.spacing(1, 1, 0.5, 1),
display: 'table',
paddingLeft: theme.spacing(2),
}),
};
};

View File

@ -1,6 +1,3 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { Stack } from '@grafana/experimental';
import React from 'react';
import { PrometheusDatasource } from '../../datasource';
@ -15,7 +12,6 @@ export interface Props {
}
export function NestedQueryList({ query, datasource, onChange, onRunQuery }: Props) {
const styles = useStyles2(getStyles);
const nestedQueries = query.binaryQueries ?? [];
const onNestedQueryUpdate = (index: number, update: PromVisualQueryBinary) => {
@ -30,44 +26,18 @@ export function NestedQueryList({ query, datasource, onChange, onRunQuery }: Pro
};
return (
<div className={styles.body}>
<Stack gap={1} direction="column">
<h5 className={styles.heading}>Binary operations</h5>
<Stack gap={1} direction="column">
{nestedQueries.map((nestedQuery, index) => (
<NestedQuery
key={index.toString()}
nestedQuery={nestedQuery}
index={index}
onChange={onNestedQueryUpdate}
datasource={datasource}
onRemove={onRemove}
onRunQuery={onRunQuery}
/>
))}
</Stack>
</Stack>
</div>
<Stack direction="column" gap={1}>
{nestedQueries.map((nestedQuery, index) => (
<NestedQuery
key={index.toString()}
nestedQuery={nestedQuery}
index={index}
onChange={onNestedQueryUpdate}
datasource={datasource}
onRemove={onRemove}
onRunQuery={onRunQuery}
/>
))}
</Stack>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
heading: css({
fontSize: 12,
fontWeight: theme.typography.fontWeightMedium,
}),
body: css({
width: '100%',
}),
connectingLine: css({
height: '2px',
width: '16px',
backgroundColor: theme.colors.border.strong,
alignSelf: 'center',
}),
addOperation: css({
paddingLeft: theme.spacing(2),
}),
};
};

View File

@ -63,7 +63,6 @@ describe('PromQueryBuilder', () => {
expect(getByText(sumBys[0], 'job')).toBeInTheDocument();
expect(getByText(sumBys[1], 'app')).toBeInTheDocument();
expect(screen.getByText('Binary operations')).toBeInTheDocument();
expect(screen.getByText('Operator')).toBeInTheDocument();
expect(screen.getByText('Vector matches')).toBeInTheDocument();
});

View File

@ -105,11 +105,11 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
onChange={onChange}
onRunQuery={onRunQuery}
/>
{query.binaryQueries && query.binaryQueries.length > 0 && (
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
)}
<PromQueryBuilderHints datasource={datasource} query={query} onChange={onChange} data={data} />
</OperationsEditorRow>
{query.binaryQueries && query.binaryQueries.length > 0 && (
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
)}
</>
);
});

View File

@ -61,10 +61,12 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<T>) {
let result = leftOperand + ` ${binaryQuery.operator} `;
if (binaryQuery.vectorMatches) {
result += `${binaryQuery.vectorMatches} `;
}
return result + `${this.renderQuery(binaryQuery.query)}`;
return result + this.renderQuery(binaryQuery.query, true);
}
renderLabels(labels: QueryBuilderLabelFilter[]) {
@ -84,5 +86,5 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
return expr + `}`;
}
abstract renderQuery(query: T): string;
abstract renderQuery(query: T, nested?: boolean): string;
}