mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
10d8ccc8ff
commit
feaa4a5c64
@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
54
public/app/plugins/datasource/loki/querybuilder/state.ts
Normal file
54
public/app/plugins/datasource/loki/querybuilder/state.ts
Normal file
@ -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…
Reference in New Issue
Block a user