Loki: Support binary operations and nested queries in new builder (#47012)

* Binary ops support

* Add support for nested expressions in loki

* Nested queries working

* Fixing tests

* Share more code between loki and prometheus query modellers
pull/47015/head^2
Torkel Ödegaard 3 years ago committed by GitHub
parent 10d8ccc8ff
commit feaa4a5c64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts
  2. 13
      public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts
  3. 115
      public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts
  4. 9
      public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx
  5. 5
      public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx
  6. 4
      public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx
  7. 125
      public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.tsx
  8. 43
      public/app/plugins/datasource/loki/querybuilder/components/NestedQueryList.tsx
  9. 24
      public/app/plugins/datasource/loki/querybuilder/operations.ts
  10. 54
      public/app/plugins/datasource/loki/querybuilder/state.ts
  11. 23
      public/app/plugins/datasource/loki/querybuilder/types.ts
  12. 30
      public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.ts
  13. 48
      public/app/plugins/datasource/prometheus/querybuilder/shared/LokiAndPromQueryModellerBase.ts

@ -112,6 +112,33 @@ describe('LokiQueryModeller', () => {
).toBe('{app="grafana"} | unwrap count');
});
it('Can render simply binary operation with scalar', () => {
expect(
modeller.renderQuery({
labels: [{ label: 'app', op: '=', value: 'grafana' }],
operations: [{ id: LokiOperationId.MultiplyBy, params: [1000] }],
})
).toBe('{app="grafana"} * 1000');
});
it('Can render query with simple binary query', () => {
expect(
modeller.renderQuery({
labels: [{ label: 'app', op: '=', value: 'grafana' }],
operations: [{ id: LokiOperationId.Rate, params: ['5m'] }],
binaryQueries: [
{
operator: '/',
query: {
labels: [{ label: 'job', op: '=', value: 'backup' }],
operations: [{ id: LokiOperationId.CountOverTime, params: ['5m'] }],
},
},
],
})
).toBe('rate({app="grafana"} [5m]) / count_over_time({job="backup"} [5m])');
});
describe('On add operation handlers', () => {
it('When adding function without range vector param should automatically add rate', () => {
const query = {

@ -1,9 +1,9 @@
import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types';
import { getOperationDefintions } from './operations';
import { LokiOperationId, LokiQueryPattern, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
import { LokiOperationId, LokiQueryPattern, LokiVisualQueryOperationCategory } from './types';
export class LokiQueryModeller extends LokiAndPromQueryModellerBase<LokiVisualQuery> {
export class LokiQueryModeller extends LokiAndPromQueryModellerBase {
constructor() {
super(getOperationDefintions);
@ -11,7 +11,7 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase<LokiVisualQu
LokiVisualQueryOperationCategory.Aggregations,
LokiVisualQueryOperationCategory.RangeFunctions,
LokiVisualQueryOperationCategory.Formats,
//LokiVisualQueryOperationCategory.Functions,
LokiVisualQueryOperationCategory.BinaryOps,
LokiVisualQueryOperationCategory.LabelFilters,
LokiVisualQueryOperationCategory.LineFilters,
]);
@ -25,13 +25,6 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase<LokiVisualQu
return super.renderLabels(labels);
}
renderQuery(query: LokiVisualQuery) {
let queryString = `${this.renderLabels(query.labels)}`;
queryString = this.renderOperations(queryString, query.operations);
queryString = this.renderBinaryQueries(queryString, query.binaryQueries);
return queryString;
}
getQueryPatterns(): LokiQueryPattern[] {
return [
{

@ -0,0 +1,115 @@
import { defaultAddOperationHandler } from '../../prometheus/querybuilder/shared/operationUtils';
import {
QueryBuilderOperation,
QueryBuilderOperationDef,
QueryBuilderOperationParamDef,
} from '../../prometheus/querybuilder/shared/types';
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
export const binaryScalarDefs = [
{
id: LokiOperationId.Addition,
name: 'Add scalar',
sign: '+',
},
{
id: LokiOperationId.Subtraction,
name: 'Subtract scalar',
sign: '-',
},
{
id: LokiOperationId.MultiplyBy,
name: 'Multiply by scalar',
sign: '*',
},
{
id: LokiOperationId.DivideBy,
name: 'Divide by scalar',
sign: '/',
},
{
id: LokiOperationId.Modulo,
name: 'Modulo by scalar',
sign: '%',
},
{
id: LokiOperationId.Exponent,
name: 'Exponent',
sign: '^',
},
{
id: LokiOperationId.EqualTo,
name: 'Equal to',
sign: '==',
comparison: true,
},
{
id: LokiOperationId.NotEqualTo,
name: 'Not equal to',
sign: '!=',
comparison: true,
},
{
id: LokiOperationId.GreaterThan,
name: 'Greater than',
sign: '>',
comparison: true,
},
{
id: LokiOperationId.LessThan,
name: 'Less than',
sign: '<',
comparison: true,
},
{
id: LokiOperationId.GreaterOrEqual,
name: 'Greater or equal to',
sign: '>=',
comparison: true,
},
{
id: LokiOperationId.LessOrEqual,
name: 'Less or equal to',
sign: '<=',
comparison: true,
},
];
// 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
export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDefs.map((opDef) => {
const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
const defaultParams: any[] = [2];
if (opDef.comparison) {
params.unshift({
name: 'Bool',
type: 'boolean',
description: 'If checked comparison will return 0 or 1 for the value rather than filtering.',
});
defaultParams.unshift(false);
}
return {
id: opDef.id,
name: opDef.name,
params,
defaultParams,
alternativesKey: 'binary scalar operations',
category: LokiVisualQueryOperationCategory.BinaryOps,
renderer: getSimpleBinaryRenderer(opDef.sign),
addOperationHandler: defaultAddOperationHandler,
};
});
function getSimpleBinaryRenderer(operator: string) {
return function binaryRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
let param = model.params[0];
let bool = '';
if (model.params.length === 2) {
param = model.params[1];
bool = model.params[0] ? ' bool' : '';
}
return `${innerExpr} ${operator}${bool} ${param}`;
};
}

@ -8,6 +8,8 @@ import { lokiQueryModeller } from '../LokiQueryModeller';
import { DataSourceApi, SelectableValue } from '@grafana/data';
import { EditorRow } from '@grafana/experimental';
import { QueryPreview } from './QueryPreview';
import { OperationsEditorRow } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow';
import { NestedQueryList } from './NestedQueryList';
export interface Props {
query: LokiVisualQuery;
@ -70,7 +72,7 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
onChange={onChangeLabels}
/>
</EditorRow>
<EditorRow>
<OperationsEditorRow>
<OperationList
queryModeller={lokiQueryModeller}
query={query}
@ -78,7 +80,10 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
onRunQuery={onRunQuery}
datasource={datasource as DataSourceApi}
/>
</EditorRow>
</OperationsEditorRow>
{query.binaryQueries && query.binaryQueries.length > 0 && (
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
)}
{!nested && (
<EditorRow>
<QueryPreview query={query} />

@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LokiDatasource } from '../../datasource';
import { cloneDeep, defaultsDeep } from 'lodash';
import { LokiQuery } from '../../types';
import { LokiQuery, LokiQueryType } from '../../types';
import { LokiQueryEditorSelector } from './LokiQueryEditorSelector';
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
@ -77,6 +77,7 @@ describe('LokiQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
queryType: LokiQueryType.Range,
editorMode: QueryEditorMode.Builder,
});
});
@ -111,6 +112,7 @@ describe('LokiQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
queryType: LokiQueryType.Range,
editorMode: QueryEditorMode.Code,
});
});
@ -121,6 +123,7 @@ describe('LokiQueryEditorSelector', () => {
expect(onChange).toBeCalledWith({
refId: 'A',
expr: defaultQuery.expr,
queryType: LokiQueryType.Range,
editorMode: QueryEditorMode.Explain,
});
});

@ -7,6 +7,7 @@ import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/
import React, { useCallback, useState } from 'react';
import { LokiQueryEditorProps } from '../../components/types';
import { lokiQueryModeller } from '../LokiQueryModeller';
import { getQueryWithDefaults } from '../state';
import { getDefaultEmptyQuery, LokiVisualQuery } from '../types';
import { LokiQueryBuilder } from './LokiQueryBuilder';
import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplaind';
@ -14,8 +15,9 @@ import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions';
import { LokiQueryCodeEditor } from './LokiQueryCodeEditor';
export const LokiQueryEditorSelector = React.memo<LokiQueryEditorProps>((props) => {
const { query, onChange, onRunQuery, data } = props;
const { onChange, onRunQuery, data } = props;
const styles = useStyles2(getStyles);
const query = getQueryWithDefaults(props.query);
const [visualQuery, setVisualQuery] = useState<LokiVisualQuery>(query.visualQuery ?? getDefaultEmptyQuery());
const onEditorModeChange = useCallback(

@ -0,0 +1,125 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, toOption } from '@grafana/data';
import { EditorRows, FlexItem } from '@grafana/experimental';
import { IconButton, Select, useStyles2 } from '@grafana/ui';
import React from 'react';
import { binaryScalarDefs } from '../binaryScalarOperations';
import { LokiVisualQueryBinary } from '../types';
import { LokiDatasource } from '../../datasource';
import { LokiQueryBuilder } from './LokiQueryBuilder';
import { AutoSizeInput } from 'app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput';
export interface Props {
nestedQuery: LokiVisualQueryBinary;
datasource: LokiDatasource;
index: number;
onChange: (index: number, update: LokiVisualQueryBinary) => void;
onRemove: (index: number) => void;
onRunQuery: () => void;
}
export const NestedQuery = React.memo<Props>(({ nestedQuery, index, datasource, onChange, onRemove, onRunQuery }) => {
const styles = useStyles2(getStyles);
return (
<div className={styles.card}>
<div className={styles.header}>
<div className={styles.name}>Operator</div>
<Select
width="auto"
options={operators}
value={toOption(nestedQuery.operator)}
onChange={(value) => {
onChange(index, {
...nestedQuery,
operator: value.value!,
});
}}
/>
<div className={styles.name}>Vector matches</div>
<div className={styles.vectorMatchWrapper}>
<Select<LokiVisualQueryBinary['vectorMatchesType']>
width="auto"
value={nestedQuery.vectorMatchesType || 'on'}
allowCustomValue
options={[
{ value: 'on', label: 'on' },
{ value: 'ignoring', label: 'ignoring' },
]}
onChange={(val) => {
onChange(index, {
...nestedQuery,
vectorMatchesType: val.value,
});
}}
/>
<AutoSizeInput
className={styles.vectorMatchInput}
minWidth={20}
defaultValue={nestedQuery.vectorMatches}
onCommitChange={(evt) => {
onChange(index, {
...nestedQuery,
vectorMatches: evt.currentTarget.value,
vectorMatchesType: nestedQuery.vectorMatchesType || 'on',
});
}}
/>
</div>
<FlexItem grow={1} />
<IconButton name="times" size="sm" onClick={() => onRemove(index)} />
</div>
<div className={styles.body}>
<EditorRows>
<LokiQueryBuilder
query={nestedQuery.query}
datasource={datasource}
nested={true}
onRunQuery={onRunQuery}
onChange={(update) => {
onChange(index, { ...nestedQuery, query: update });
}}
/>
</EditorRows>
</div>
</div>
);
});
const operators = binaryScalarDefs.map((def) => ({ label: def.sign, value: def.sign }));
NestedQuery.displayName = 'NestedQuery';
const getStyles = (theme: GrafanaTheme2) => {
return {
card: css({
label: 'card',
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
}),
header: css({
label: 'header',
padding: theme.spacing(0.5, 0.5, 0.5, 1),
gap: theme.spacing(1),
display: 'flex',
alignItems: 'center',
}),
name: css({
label: 'name',
whiteSpace: 'nowrap',
}),
body: css({
label: 'body',
paddingLeft: theme.spacing(2),
}),
vectorMatchInput: css({
label: 'vectorMatchInput',
marginLeft: -1,
}),
vectorMatchWrapper: css({
label: 'vectorMatchWrapper',
display: 'flex',
}),
};
};

@ -0,0 +1,43 @@
import { Stack } from '@grafana/experimental';
import React from 'react';
import { LokiDatasource } from '../../datasource';
import { LokiVisualQuery, LokiVisualQueryBinary } from '../types';
import { NestedQuery } from './NestedQuery';
export interface Props {
query: LokiVisualQuery;
datasource: LokiDatasource;
onChange: (query: LokiVisualQuery) => void;
onRunQuery: () => void;
}
export function NestedQueryList({ query, datasource, onChange, onRunQuery }: Props) {
const nestedQueries = query.binaryQueries ?? [];
const onNestedQueryUpdate = (index: number, update: LokiVisualQueryBinary) => {
const updatedList = [...nestedQueries];
updatedList.splice(index, 1, update);
onChange({ ...query, binaryQueries: updatedList });
};
const onRemove = (index: number) => {
const updatedList = [...nestedQueries.slice(0, index), ...nestedQueries.slice(index + 1)];
onChange({ ...query, binaryQueries: updatedList });
};
return (
<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>
);
}

@ -7,6 +7,7 @@ import {
VisualQueryModeller,
} from '../../prometheus/querybuilder/shared/types';
import { FUNCTIONS } from '../syntax';
import { binaryScalarOperations } from './binaryScalarOperations';
import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
export function getOperationDefintions(): QueryBuilderOperationDef[] {
@ -180,6 +181,16 @@ export function getOperationDefintions(): QueryBuilderOperationDef[] {
return `Use the extracted label \`${label}\` as sample values instead of log lines for the subsequent range aggregation.`;
},
},
...binaryScalarOperations,
{
id: LokiOperationId.NestedQuery,
name: 'Binary operation with query',
params: [],
defaultParams: [],
category: LokiVisualQueryOperationCategory.BinaryOps,
renderer: (model, def, innerExpr) => innerExpr,
addOperationHandler: addNestedQueryHandler,
},
];
return list;
@ -328,3 +339,16 @@ export function addLokiOperation(
operations,
};
}
function addNestedQueryHandler(def: QueryBuilderOperationDef, query: LokiVisualQuery): LokiVisualQuery {
return {
...query,
binaryQueries: [
...(query.binaryQueries ?? []),
{
operator: '/',
query,
},
],
};
}

@ -0,0 +1,54 @@
import store from 'app/core/store';
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
import { LokiQuery, LokiQueryType } from '../types';
const queryEditorModeDefaultLocalStorageKey = 'LokiQueryEditorModeDefault';
export function changeEditorMode(query: LokiQuery, editorMode: QueryEditorMode, onChange: (query: LokiQuery) => void) {
// If empty query store new mode as default
if (query.expr === '') {
store.set(queryEditorModeDefaultLocalStorageKey, editorMode);
}
onChange({ ...query, editorMode });
}
export function getDefaultEditorMode(expr: string) {
// If we already have an expression default to code view
if (expr != null && expr !== '') {
return QueryEditorMode.Code;
}
const value = store.get(queryEditorModeDefaultLocalStorageKey) as QueryEditorMode;
switch (value) {
case QueryEditorMode.Builder:
case QueryEditorMode.Code:
case QueryEditorMode.Explain:
return value;
default:
return QueryEditorMode.Builder;
}
}
/**
* Returns query with defaults, and boolean true/false depending on change was required
*/
export function getQueryWithDefaults(query: LokiQuery): LokiQuery {
// If no expr (ie new query) then default to builder
let result = query;
if (!query.editorMode) {
result = { ...query, editorMode: getDefaultEditorMode(query.expr) };
}
if (query.expr == null) {
result = { ...result, expr: '' };
}
if (query.queryType == null) {
// Default to range query
result = { ...result, queryType: LokiQueryType.Range };
}
return result;
}

@ -1,3 +1,4 @@
import { VisualQueryBinary } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
import { QueryBuilderLabelFilter, QueryBuilderOperation } from '../../prometheus/querybuilder/shared/types';
/**
@ -9,11 +10,8 @@ export interface LokiVisualQuery {
binaryQueries?: LokiVisualQueryBinary[];
}
export interface LokiVisualQueryBinary {
operator: string;
vectorMatches?: string;
query: LokiVisualQuery;
}
export type LokiVisualQueryBinary = VisualQueryBinary<LokiVisualQuery>;
export interface LokiQueryPattern {
name: string;
operations: QueryBuilderOperation[];
@ -26,6 +24,7 @@ export enum LokiVisualQueryOperationCategory {
Formats = 'Formats',
LineFilters = 'Line filters',
LabelFilters = 'Label filters',
BinaryOps = 'Binary operations',
}
export enum LokiOperationId {
@ -50,6 +49,20 @@ export enum LokiOperationId {
LabelFilter = '__label_filter',
LabelFilterNoErrors = '__label_filter_no_errors',
Unwrap = 'unwrap',
// Binary ops
Addition = '__addition',
Subtraction = '__subtraction',
MultiplyBy = '__multiply_by',
DivideBy = '__divide_by',
Modulo = '__modulo',
Exponent = '__exponent',
NestedQuery = '__nested_query',
EqualTo = '__equal_to',
NotEqualTo = '__not_equal_to',
GreaterThan = '__greater_than',
LessThan = '__less_than',
GreaterOrEqual = '__greater_or_equal',
LessOrEqual = '__less_or_equal',
}
export enum LokiOperationOrder {

@ -2,9 +2,9 @@ import { FUNCTIONS } from '../promql';
import { getAggregationOperations } from './aggregations';
import { getOperationDefinitions } from './operations';
import { LokiAndPromQueryModellerBase } from './shared/LokiAndPromQueryModellerBase';
import { PromQueryPattern, PromVisualQuery, PromVisualQueryOperationCategory } from './types';
import { PromQueryPattern, PromVisualQueryOperationCategory } from './types';
export class PromQueryModeller extends LokiAndPromQueryModellerBase<PromVisualQuery> {
export class PromQueryModeller extends LokiAndPromQueryModellerBase {
constructor() {
super(() => {
const allOperations = [...getOperationDefinitions(), ...getAggregationOperations()];
@ -27,32 +27,6 @@ export class PromQueryModeller extends LokiAndPromQueryModellerBase<PromVisualQu
]);
}
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 [
{

@ -1,11 +1,6 @@
import { Registry } from '@grafana/data';
import {
QueryBuilderLabelFilter,
QueryBuilderOperation,
QueryBuilderOperationDef,
QueryWithOperations,
VisualQueryModeller,
} from './types';
import { PromVisualQueryOperationCategory } from '../types';
import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryBuilderOperationDef, VisualQueryModeller } from './types';
export interface VisualQueryBinary<T> {
operator: string;
@ -14,7 +9,14 @@ export interface VisualQueryBinary<T> {
query: T;
}
export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations> implements VisualQueryModeller {
export interface PromLokiVisualQuery {
metric?: string;
labels: QueryBuilderLabelFilter[];
operations: QueryBuilderOperation[];
binaryQueries?: Array<VisualQueryBinary<PromLokiVisualQuery>>;
}
export abstract class LokiAndPromQueryModellerBase implements VisualQueryModeller {
protected operationsRegisty: Registry<QueryBuilderOperationDef>;
private categories: string[] = [];
@ -54,7 +56,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
return queryString;
}
renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<T>>) {
renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<PromLokiVisualQuery>>) {
if (binaryQueries) {
for (const binQuery of binaryQueries) {
queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
@ -63,7 +65,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
return queryString;
}
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<T>) {
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<PromLokiVisualQuery>) {
let result = leftOperand + ` ${binaryQuery.operator} `;
if (binaryQuery.vectorMatches) {
@ -90,5 +92,29 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
return expr + `}`;
}
abstract renderQuery(query: T, nested?: boolean): string;
renderQuery(query: PromLokiVisualQuery, 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: PromLokiVisualQuery): boolean {
return (
query.operations.find((op) => {
const def = this.getOperationDef(op.id);
return def?.category === PromVisualQueryOperationCategory.BinaryOps;
}) !== undefined
);
}
}

Loading…
Cancel
Save