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');
|
).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', () => {
|
describe('On add operation handlers', () => {
|
||||||
it('When adding function without range vector param should automatically add rate', () => {
|
it('When adding function without range vector param should automatically add rate', () => {
|
||||||
const query = {
|
const query = {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
|
import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
|
||||||
import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types';
|
import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types';
|
||||||
import { getOperationDefintions } from './operations';
|
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() {
|
constructor() {
|
||||||
super(getOperationDefintions);
|
super(getOperationDefintions);
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase<LokiVisualQu
|
|||||||
LokiVisualQueryOperationCategory.Aggregations,
|
LokiVisualQueryOperationCategory.Aggregations,
|
||||||
LokiVisualQueryOperationCategory.RangeFunctions,
|
LokiVisualQueryOperationCategory.RangeFunctions,
|
||||||
LokiVisualQueryOperationCategory.Formats,
|
LokiVisualQueryOperationCategory.Formats,
|
||||||
//LokiVisualQueryOperationCategory.Functions,
|
LokiVisualQueryOperationCategory.BinaryOps,
|
||||||
LokiVisualQueryOperationCategory.LabelFilters,
|
LokiVisualQueryOperationCategory.LabelFilters,
|
||||||
LokiVisualQueryOperationCategory.LineFilters,
|
LokiVisualQueryOperationCategory.LineFilters,
|
||||||
]);
|
]);
|
||||||
@ -25,13 +25,6 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase<LokiVisualQu
|
|||||||
return super.renderLabels(labels);
|
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[] {
|
getQueryPatterns(): LokiQueryPattern[] {
|
||||||
return [
|
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 { DataSourceApi, SelectableValue } from '@grafana/data';
|
||||||
import { EditorRow } from '@grafana/experimental';
|
import { EditorRow } from '@grafana/experimental';
|
||||||
import { QueryPreview } from './QueryPreview';
|
import { QueryPreview } from './QueryPreview';
|
||||||
|
import { OperationsEditorRow } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow';
|
||||||
|
import { NestedQueryList } from './NestedQueryList';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
query: LokiVisualQuery;
|
query: LokiVisualQuery;
|
||||||
@ -70,7 +72,7 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
|
|||||||
onChange={onChangeLabels}
|
onChange={onChangeLabels}
|
||||||
/>
|
/>
|
||||||
</EditorRow>
|
</EditorRow>
|
||||||
<EditorRow>
|
<OperationsEditorRow>
|
||||||
<OperationList
|
<OperationList
|
||||||
queryModeller={lokiQueryModeller}
|
queryModeller={lokiQueryModeller}
|
||||||
query={query}
|
query={query}
|
||||||
@ -78,7 +80,10 @@ export const LokiQueryBuilder = React.memo<Props>(({ datasource, query, nested,
|
|||||||
onRunQuery={onRunQuery}
|
onRunQuery={onRunQuery}
|
||||||
datasource={datasource as DataSourceApi}
|
datasource={datasource as DataSourceApi}
|
||||||
/>
|
/>
|
||||||
</EditorRow>
|
</OperationsEditorRow>
|
||||||
|
{query.binaryQueries && query.binaryQueries.length > 0 && (
|
||||||
|
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
|
||||||
|
)}
|
||||||
{!nested && (
|
{!nested && (
|
||||||
<EditorRow>
|
<EditorRow>
|
||||||
<QueryPreview query={query} />
|
<QueryPreview query={query} />
|
||||||
|
@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { LokiDatasource } from '../../datasource';
|
import { LokiDatasource } from '../../datasource';
|
||||||
import { cloneDeep, defaultsDeep } from 'lodash';
|
import { cloneDeep, defaultsDeep } from 'lodash';
|
||||||
import { LokiQuery } from '../../types';
|
import { LokiQuery, LokiQueryType } from '../../types';
|
||||||
import { LokiQueryEditorSelector } from './LokiQueryEditorSelector';
|
import { LokiQueryEditorSelector } from './LokiQueryEditorSelector';
|
||||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||||
|
|
||||||
@ -77,6 +77,7 @@ describe('LokiQueryEditorSelector', () => {
|
|||||||
expect(onChange).toBeCalledWith({
|
expect(onChange).toBeCalledWith({
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: defaultQuery.expr,
|
expr: defaultQuery.expr,
|
||||||
|
queryType: LokiQueryType.Range,
|
||||||
editorMode: QueryEditorMode.Builder,
|
editorMode: QueryEditorMode.Builder,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -111,6 +112,7 @@ describe('LokiQueryEditorSelector', () => {
|
|||||||
expect(onChange).toBeCalledWith({
|
expect(onChange).toBeCalledWith({
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: defaultQuery.expr,
|
expr: defaultQuery.expr,
|
||||||
|
queryType: LokiQueryType.Range,
|
||||||
editorMode: QueryEditorMode.Code,
|
editorMode: QueryEditorMode.Code,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -121,6 +123,7 @@ describe('LokiQueryEditorSelector', () => {
|
|||||||
expect(onChange).toBeCalledWith({
|
expect(onChange).toBeCalledWith({
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
expr: defaultQuery.expr,
|
expr: defaultQuery.expr,
|
||||||
|
queryType: LokiQueryType.Range,
|
||||||
editorMode: QueryEditorMode.Explain,
|
editorMode: QueryEditorMode.Explain,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,6 +7,7 @@ import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { LokiQueryEditorProps } from '../../components/types';
|
import { LokiQueryEditorProps } from '../../components/types';
|
||||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||||
|
import { getQueryWithDefaults } from '../state';
|
||||||
import { getDefaultEmptyQuery, LokiVisualQuery } from '../types';
|
import { getDefaultEmptyQuery, LokiVisualQuery } from '../types';
|
||||||
import { LokiQueryBuilder } from './LokiQueryBuilder';
|
import { LokiQueryBuilder } from './LokiQueryBuilder';
|
||||||
import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplaind';
|
import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplaind';
|
||||||
@ -14,8 +15,9 @@ import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions';
|
|||||||
import { LokiQueryCodeEditor } from './LokiQueryCodeEditor';
|
import { LokiQueryCodeEditor } from './LokiQueryCodeEditor';
|
||||||
|
|
||||||
export const LokiQueryEditorSelector = React.memo<LokiQueryEditorProps>((props) => {
|
export const LokiQueryEditorSelector = React.memo<LokiQueryEditorProps>((props) => {
|
||||||
const { query, onChange, onRunQuery, data } = props;
|
const { onChange, onRunQuery, data } = props;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const query = getQueryWithDefaults(props.query);
|
||||||
const [visualQuery, setVisualQuery] = useState<LokiVisualQuery>(query.visualQuery ?? getDefaultEmptyQuery());
|
const [visualQuery, setVisualQuery] = useState<LokiVisualQuery>(query.visualQuery ?? getDefaultEmptyQuery());
|
||||||
|
|
||||||
const onEditorModeChange = useCallback(
|
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,
|
VisualQueryModeller,
|
||||||
} from '../../prometheus/querybuilder/shared/types';
|
} from '../../prometheus/querybuilder/shared/types';
|
||||||
import { FUNCTIONS } from '../syntax';
|
import { FUNCTIONS } from '../syntax';
|
||||||
|
import { binaryScalarOperations } from './binaryScalarOperations';
|
||||||
import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
|
import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
|
||||||
|
|
||||||
export function getOperationDefintions(): QueryBuilderOperationDef[] {
|
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.`;
|
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;
|
return list;
|
||||||
@ -328,3 +339,16 @@ export function addLokiOperation(
|
|||||||
operations,
|
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';
|
import { QueryBuilderLabelFilter, QueryBuilderOperation } from '../../prometheus/querybuilder/shared/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,11 +10,8 @@ export interface LokiVisualQuery {
|
|||||||
binaryQueries?: LokiVisualQueryBinary[];
|
binaryQueries?: LokiVisualQueryBinary[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LokiVisualQueryBinary {
|
export type LokiVisualQueryBinary = VisualQueryBinary<LokiVisualQuery>;
|
||||||
operator: string;
|
|
||||||
vectorMatches?: string;
|
|
||||||
query: LokiVisualQuery;
|
|
||||||
}
|
|
||||||
export interface LokiQueryPattern {
|
export interface LokiQueryPattern {
|
||||||
name: string;
|
name: string;
|
||||||
operations: QueryBuilderOperation[];
|
operations: QueryBuilderOperation[];
|
||||||
@ -26,6 +24,7 @@ export enum LokiVisualQueryOperationCategory {
|
|||||||
Formats = 'Formats',
|
Formats = 'Formats',
|
||||||
LineFilters = 'Line filters',
|
LineFilters = 'Line filters',
|
||||||
LabelFilters = 'Label filters',
|
LabelFilters = 'Label filters',
|
||||||
|
BinaryOps = 'Binary operations',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LokiOperationId {
|
export enum LokiOperationId {
|
||||||
@ -50,6 +49,20 @@ export enum LokiOperationId {
|
|||||||
LabelFilter = '__label_filter',
|
LabelFilter = '__label_filter',
|
||||||
LabelFilterNoErrors = '__label_filter_no_errors',
|
LabelFilterNoErrors = '__label_filter_no_errors',
|
||||||
Unwrap = 'unwrap',
|
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 {
|
export enum LokiOperationOrder {
|
||||||
|
@ -2,9 +2,9 @@ import { FUNCTIONS } from '../promql';
|
|||||||
import { getAggregationOperations } from './aggregations';
|
import { getAggregationOperations } from './aggregations';
|
||||||
import { getOperationDefinitions } from './operations';
|
import { getOperationDefinitions } from './operations';
|
||||||
import { LokiAndPromQueryModellerBase } from './shared/LokiAndPromQueryModellerBase';
|
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() {
|
constructor() {
|
||||||
super(() => {
|
super(() => {
|
||||||
const allOperations = [...getOperationDefinitions(), ...getAggregationOperations()];
|
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[] {
|
getQueryPatterns(): PromQueryPattern[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { Registry } from '@grafana/data';
|
import { Registry } from '@grafana/data';
|
||||||
import {
|
import { PromVisualQueryOperationCategory } from '../types';
|
||||||
QueryBuilderLabelFilter,
|
import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryBuilderOperationDef, VisualQueryModeller } from './types';
|
||||||
QueryBuilderOperation,
|
|
||||||
QueryBuilderOperationDef,
|
|
||||||
QueryWithOperations,
|
|
||||||
VisualQueryModeller,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
export interface VisualQueryBinary<T> {
|
export interface VisualQueryBinary<T> {
|
||||||
operator: string;
|
operator: string;
|
||||||
@ -14,7 +9,14 @@ export interface VisualQueryBinary<T> {
|
|||||||
query: 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>;
|
protected operationsRegisty: Registry<QueryBuilderOperationDef>;
|
||||||
private categories: string[] = [];
|
private categories: string[] = [];
|
||||||
|
|
||||||
@ -54,7 +56,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
|
|||||||
return queryString;
|
return queryString;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<T>>) {
|
renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<PromLokiVisualQuery>>) {
|
||||||
if (binaryQueries) {
|
if (binaryQueries) {
|
||||||
for (const binQuery of binaryQueries) {
|
for (const binQuery of binaryQueries) {
|
||||||
queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
|
queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
|
||||||
@ -63,7 +65,7 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
|
|||||||
return queryString;
|
return queryString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<T>) {
|
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<PromLokiVisualQuery>) {
|
||||||
let result = leftOperand + ` ${binaryQuery.operator} `;
|
let result = leftOperand + ` ${binaryQuery.operator} `;
|
||||||
|
|
||||||
if (binaryQuery.vectorMatches) {
|
if (binaryQuery.vectorMatches) {
|
||||||
@ -90,5 +92,29 @@ export abstract class LokiAndPromQueryModellerBase<T extends QueryWithOperations
|
|||||||
return expr + `}`;
|
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