mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Implement visual query builder from @grafana/experimental
(#81140)
* Loki: Use visual query builder from grafana/experimental * Update to 1.7.7 * Update * In renderOperation console.error instead of throwing error * Remove redundant comment
This commit is contained in:
parent
4577e61ee7
commit
c6793d4f12
@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { dateTime, GrafanaTheme2, LogRowModel, renderMarkdown, SelectableValue } from '@grafana/data';
|
||||
import { RawQuery } from '@grafana/experimental';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import {
|
||||
Alert,
|
||||
@ -20,7 +21,6 @@ import {
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { RawQuery } from '../../prometheus/querybuilder/shared/RawQuery';
|
||||
import {
|
||||
LogContextProvider,
|
||||
LOKI_LOG_CONTEXT_PRESERVED_LABELS,
|
||||
@ -312,7 +312,11 @@ export function LokiContextUi(props: LokiContextUiProps) {
|
||||
<div className={styles.rawQueryContainer}>
|
||||
{initialized ? (
|
||||
<>
|
||||
<RawQuery lang={{ grammar: lokiGrammar, name: 'loki' }} query={queryExpr} className={styles.rawQuery} />
|
||||
<RawQuery
|
||||
language={{ grammar: lokiGrammar, name: 'loki' }}
|
||||
query={queryExpr}
|
||||
className={styles.rawQuery}
|
||||
/>
|
||||
<Tooltip content="The initial log context query is created from all labels defining the stream for the selected log line. Use the editor below to customize the log context query.">
|
||||
<Icon name="info-circle" size="sm" className={styles.queryDescription} />
|
||||
</Tooltip>
|
||||
|
@ -4,7 +4,7 @@ import { cloneDeep, defaultsDeep } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { CoreApp } from '@grafana/data';
|
||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||
import { QueryEditorMode } from '@grafana/experimental';
|
||||
|
||||
import { createLokiDatasource } from '../__mocks__/datasource';
|
||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained';
|
||||
|
@ -4,12 +4,16 @@ import { usePrevious } from 'react-use';
|
||||
|
||||
import { CoreApp, LoadingState } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { EditorHeader, EditorRows, FlexItem } from '@grafana/experimental';
|
||||
import {
|
||||
EditorHeader,
|
||||
EditorRows,
|
||||
FlexItem,
|
||||
QueryEditorModeToggle,
|
||||
QueryHeaderSwitch,
|
||||
QueryEditorMode,
|
||||
} from '@grafana/experimental';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, ConfirmModal, Space, Stack } from '@grafana/ui';
|
||||
import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle';
|
||||
import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch';
|
||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||
|
||||
import { LabelBrowserModal } from '../querybuilder/components/LabelBrowserModal';
|
||||
import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NodeType, SyntaxNode } from '@lezer/common';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import { QueryBuilderLabelFilter } from '@grafana/experimental';
|
||||
import {
|
||||
Identifier,
|
||||
LabelFilter,
|
||||
@ -22,8 +23,6 @@ import {
|
||||
Expr,
|
||||
} from '@grafana/lezer-logql';
|
||||
|
||||
import { QueryBuilderLabelFilter } from '../prometheus/querybuilder/shared/types';
|
||||
|
||||
import { unescapeLabelValue } from './languageUtils';
|
||||
import { getNodePositionsFromQuery } from './queryUtils';
|
||||
import { lokiQueryModeller as modeller } from './querybuilder/LokiQueryModeller';
|
||||
|
@ -254,7 +254,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef('sum')!;
|
||||
const def = modeller.getOperationDefinition('sum')!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[0].id).toBe('rate');
|
||||
expect(result.operations[1].id).toBe('sum');
|
||||
@ -266,7 +266,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [{ id: LokiOperationId.Json, params: [] }],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef('sum')!;
|
||||
const def = modeller.getOperationDefinition('sum')!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[0].id).toBe(LokiOperationId.Json);
|
||||
expect(result.operations[1].id).toBe('rate');
|
||||
@ -279,7 +279,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [{ id: 'rate', params: [] }],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef(LokiOperationId.Json)!;
|
||||
const def = modeller.getOperationDefinition(LokiOperationId.Json)!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[0].id).toBe(LokiOperationId.Json);
|
||||
expect(result.operations[1].id).toBe('rate');
|
||||
@ -291,7 +291,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [{ id: LokiOperationId.LineContains, params: ['error'] }],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef(LokiOperationId.Json)!;
|
||||
const def = modeller.getOperationDefinition(LokiOperationId.Json)!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
|
||||
expect(result.operations[1].id).toBe(LokiOperationId.Json);
|
||||
@ -303,7 +303,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [{ id: LokiOperationId.Json, params: [] }],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef(LokiOperationId.LineContains)!;
|
||||
const def = modeller.getOperationDefinition(LokiOperationId.LineContains)!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[0].id).toBe(LokiOperationId.LineContains);
|
||||
expect(result.operations[1].id).toBe(LokiOperationId.Json);
|
||||
@ -315,7 +315,7 @@ describe('LokiQueryModeller', () => {
|
||||
operations: [{ id: LokiOperationId.Rate, params: [] }],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef(LokiOperationId.Rate)!;
|
||||
const def = modeller.getOperationDefinition(LokiOperationId.Rate)!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations.length).toBe(1);
|
||||
});
|
||||
@ -329,7 +329,7 @@ describe('LokiQueryModeller', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const def = modeller.getOperationDef(LokiOperationId.Unwrap)!;
|
||||
const def = modeller.getOperationDefinition(LokiOperationId.Unwrap)!;
|
||||
const result = def.addOperationHandler(def, query, modeller);
|
||||
expect(result.operations[1].id).toBe(LokiOperationId.Unwrap);
|
||||
});
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { LokiAndPromQueryModellerBase } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
|
||||
import { QueryBuilderLabelFilter } from '../../prometheus/querybuilder/shared/types';
|
||||
import {
|
||||
QueryModellerBase,
|
||||
QueryBuilderLabelFilter,
|
||||
VisualQuery,
|
||||
QueryBuilderOperation,
|
||||
VisualQueryBinary,
|
||||
} from '@grafana/experimental';
|
||||
|
||||
import { getOperationDefinitions } from './operations';
|
||||
import { operationDefinitions } from './operations';
|
||||
import { LokiOperationId, LokiQueryPattern, LokiQueryPatternType, LokiVisualQueryOperationCategory } from './types';
|
||||
|
||||
export class LokiQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
export class LokiQueryModeller extends QueryModellerBase {
|
||||
constructor() {
|
||||
super(getOperationDefinitions);
|
||||
super(operationDefinitions, '<expr>');
|
||||
|
||||
this.setOperationCategories([
|
||||
LokiVisualQueryOperationCategory.Aggregations,
|
||||
@ -18,12 +23,65 @@ export class LokiQueryModeller extends LokiAndPromQueryModellerBase {
|
||||
]);
|
||||
}
|
||||
|
||||
renderLabels(labels: QueryBuilderLabelFilter[]) {
|
||||
renderOperations(queryString: string, operations: QueryBuilderOperation[]): string {
|
||||
for (const operation of operations) {
|
||||
const def = this.operationsRegistry.getIfExists(operation.id);
|
||||
if (!def) {
|
||||
console.error(`Could not find operation ${operation.id} in the registry`);
|
||||
continue;
|
||||
}
|
||||
queryString = def.renderer(operation, def, queryString);
|
||||
}
|
||||
return queryString;
|
||||
}
|
||||
|
||||
renderBinaryQueries(queryString: string, binaryQueries?: Array<VisualQueryBinary<VisualQuery>>) {
|
||||
if (binaryQueries) {
|
||||
for (const binQuery of binaryQueries) {
|
||||
queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
|
||||
}
|
||||
}
|
||||
return queryString;
|
||||
}
|
||||
|
||||
private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary<VisualQuery>) {
|
||||
let result = leftOperand + ` ${binaryQuery.operator} `;
|
||||
|
||||
if (binaryQuery.vectorMatches) {
|
||||
result += `${binaryQuery.vectorMatchesType}(${binaryQuery.vectorMatches}) `;
|
||||
}
|
||||
|
||||
return result + this.renderQuery(binaryQuery.query, true);
|
||||
}
|
||||
|
||||
renderLabels(labels: QueryBuilderLabelFilter[]): string {
|
||||
if (labels.length === 0) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
return super.renderLabels(labels);
|
||||
let expr = '{';
|
||||
for (const filter of labels) {
|
||||
if (expr !== '{') {
|
||||
expr += ', ';
|
||||
}
|
||||
|
||||
expr += `${filter.label}${filter.op}"${filter.value}"`;
|
||||
}
|
||||
|
||||
return expr + `}`;
|
||||
}
|
||||
|
||||
renderQuery(query: VisualQuery, nested?: boolean): string {
|
||||
let queryString = 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);
|
||||
|
||||
return queryString;
|
||||
}
|
||||
|
||||
getQueryPatterns(): LokiQueryPattern[] {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
QueryBuilderOperation,
|
||||
QueryBuilderOperationDef,
|
||||
QueryBuilderOperationDefinition,
|
||||
QueryBuilderOperationParamDef,
|
||||
} from '../../prometheus/querybuilder/shared/types';
|
||||
} from '@grafana/experimental';
|
||||
|
||||
import { defaultAddOperationHandler } from './operationUtils';
|
||||
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
|
||||
@ -78,7 +78,7 @@ export const binaryScalarDefs = [
|
||||
|
||||
// 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) => {
|
||||
export const binaryScalarOperations: QueryBuilderOperationDefinition[] = binaryScalarDefs.map((opDef) => {
|
||||
const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
|
||||
const defaultParams: any[] = [2];
|
||||
if (opDef.comparison) {
|
||||
@ -103,7 +103,11 @@ export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDe
|
||||
});
|
||||
|
||||
function getSimpleBinaryRenderer(operator: string) {
|
||||
return function binaryRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function binaryRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
let param = model.params[0];
|
||||
let bool = '';
|
||||
if (model.params.length === 2) {
|
||||
|
@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataSourceApi, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
QueryBuilderLabelFilter,
|
||||
QueryBuilderOperationParamEditorProps,
|
||||
QueryBuilderOperationParamValue,
|
||||
VisualQuery,
|
||||
VisualQueryModeller,
|
||||
} from '@grafana/experimental';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { getOperationParamId } from '../operationUtils';
|
||||
|
||||
export const LabelParamEditor = ({
|
||||
onChange,
|
||||
index,
|
||||
operationId,
|
||||
value,
|
||||
query,
|
||||
datasource,
|
||||
queryModeller,
|
||||
}: QueryBuilderOperationParamEditorProps) => {
|
||||
const [state, setState] = useState<{
|
||||
options?: SelectableValue[];
|
||||
isLoading?: boolean;
|
||||
}>({});
|
||||
|
||||
return (
|
||||
<Select<QueryBuilderOperationParamValue | undefined>
|
||||
inputId={getOperationParamId(operationId, index)}
|
||||
autoFocus={value === ''}
|
||||
openMenuOnFocus
|
||||
onOpenMenu={async () => {
|
||||
setState({ isLoading: true });
|
||||
const options = await loadGroupByLabels(query, datasource, queryModeller);
|
||||
setState({ options, isLoading: undefined });
|
||||
}}
|
||||
isLoading={state.isLoading}
|
||||
allowCustomValue
|
||||
noOptionsMessage="No labels found"
|
||||
loadingMessage="Loading labels"
|
||||
options={state.options}
|
||||
value={toOption(value)}
|
||||
onChange={(value) => onChange(index, value.value!)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
async function loadGroupByLabels(
|
||||
query: VisualQuery,
|
||||
datasource: DataSourceApi,
|
||||
queryModeller: VisualQueryModeller
|
||||
): Promise<SelectableValue[]> {
|
||||
let labels: QueryBuilderLabelFilter[] = query.labels;
|
||||
|
||||
const queryString = queryModeller.renderLabels(labels);
|
||||
const result = await datasource.languageProvider.fetchSeriesLabels(queryString);
|
||||
|
||||
return Object.keys(result).map((x) => ({
|
||||
label: x,
|
||||
value: x,
|
||||
}));
|
||||
}
|
||||
|
||||
const toOption = (
|
||||
value: QueryBuilderOperationParamValue | undefined
|
||||
): SelectableValue<QueryBuilderOperationParamValue | undefined> => ({ label: value?.toString(), value });
|
@ -5,13 +5,13 @@ import { getSelectParent } from 'test/helpers/selectOptionInTest';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
|
||||
import { MISSING_LABEL_FILTER_ERROR_MESSAGE } from '../../../prometheus/querybuilder/shared/LabelFilters';
|
||||
import { createLokiDatasource } from '../../__mocks__/datasource';
|
||||
import { LokiOperationId, LokiVisualQuery } from '../types';
|
||||
|
||||
import { LokiQueryBuilder } from './LokiQueryBuilder';
|
||||
import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained';
|
||||
|
||||
const MISSING_LABEL_FILTER_ERROR_MESSAGE = 'Select at least 1 label filter (label and value)';
|
||||
const defaultQuery: LokiVisualQuery = {
|
||||
labels: [{ op: '=', label: 'baz', value: 'bar' }],
|
||||
operations: [],
|
||||
|
@ -1,25 +1,28 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { DataSourceApi, getDefaultTimeRange, LoadingState, PanelData, SelectableValue, TimeRange } from '@grafana/data';
|
||||
import { EditorRow } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
|
||||
import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox';
|
||||
import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList';
|
||||
import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained';
|
||||
import { OperationsEditorRow } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationsEditorRow';
|
||||
import { QueryBuilderHints } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryBuilderHints';
|
||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
|
||||
import {
|
||||
EditorRow,
|
||||
// LabelFilters, this is broken in @grafana/experimental so we need to use the one from prometheus
|
||||
OperationExplainedBox,
|
||||
OperationList,
|
||||
OperationListExplained,
|
||||
OperationsEditorRow,
|
||||
QueryBuilderHints,
|
||||
RawQuery,
|
||||
QueryBuilderLabelFilter,
|
||||
QueryBuilderOperation,
|
||||
} from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||
} from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { LabelFilters } from 'app/plugins/datasource/prometheus/querybuilder/shared/LabelFilters';
|
||||
|
||||
import { testIds } from '../../components/LokiQueryEditor';
|
||||
import { LokiDatasource } from '../../datasource';
|
||||
import { escapeLabelValueInSelector } from '../../languageUtils';
|
||||
import logqlGrammar from '../../syntax';
|
||||
import { LokiQuery } from '../../types';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
import { isConflictingFilter } from '../operationUtils';
|
||||
import { buildVisualQueryFromString } from '../parsing';
|
||||
import { LokiOperationId, LokiVisualQuery } from '../types';
|
||||
|
||||
@ -130,7 +133,7 @@ export const LokiQueryBuilder = React.memo<Props>(
|
||||
{showExplain && (
|
||||
<OperationExplainedBox
|
||||
stepNumber={1}
|
||||
title={<RawQuery query={`${lokiQueryModeller.renderLabels(query.labels)}`} lang={lang} />}
|
||||
title={<RawQuery query={`${lokiQueryModeller.renderLabels(query.labels)}`} language={lang} />}
|
||||
>
|
||||
{EXPLAIN_LABEL_FILTER_CONTENT}
|
||||
</OperationExplainedBox>
|
||||
@ -143,14 +146,19 @@ export const LokiQueryBuilder = React.memo<Props>(
|
||||
onRunQuery={onRunQuery}
|
||||
datasource={datasource as DataSourceApi}
|
||||
highlightedOp={highlightedOp}
|
||||
isConflictingOperation={(operation: QueryBuilderOperation, otherOperations: QueryBuilderOperation[]) =>
|
||||
operation.id === LokiOperationId.LabelFilter && isConflictingFilter(operation, otherOperations)
|
||||
}
|
||||
/>
|
||||
<QueryBuilderHints<LokiVisualQuery>
|
||||
<QueryBuilderHints<LokiVisualQuery, LokiQuery>
|
||||
datasource={datasource}
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
data={sampleData}
|
||||
queryModeller={lokiQueryModeller}
|
||||
buildVisualQueryFromString={buildVisualQueryFromString}
|
||||
buildDataQueryFromQueryString={(queryString) => ({ expr: queryString, refId: 'hints' })}
|
||||
buildQueryStringFromDataQuery={(query) => query.expr}
|
||||
/>
|
||||
</OperationsEditorRow>
|
||||
{showExplain && (
|
||||
@ -158,7 +166,7 @@ export const LokiQueryBuilder = React.memo<Props>(
|
||||
stepNumber={2}
|
||||
queryModeller={lokiQueryModeller}
|
||||
query={query}
|
||||
lang={lang}
|
||||
language={lang}
|
||||
onMouseEnter={(op) => {
|
||||
setHighlightedOp(op);
|
||||
}}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { OperationExplainedBox, OperationListExplained, RawQuery } from '@grafana/experimental';
|
||||
import { Stack } from '@grafana/ui';
|
||||
import { OperationExplainedBox } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationExplainedBox';
|
||||
import { OperationListExplained } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationListExplained';
|
||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
|
||||
|
||||
import { lokiGrammar } from '../../syntax';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
@ -24,7 +22,7 @@ export const LokiQueryBuilderExplained = React.memo<Props>(({ query }) => {
|
||||
<Stack gap={0} direction="column">
|
||||
<OperationExplainedBox
|
||||
stepNumber={1}
|
||||
title={<RawQuery query={`${lokiQueryModeller.renderLabels(visQuery.labels)}`} lang={lang} />}
|
||||
title={<RawQuery query={`${lokiQueryModeller.renderLabels(visQuery.labels)}`} language={lang} />}
|
||||
>
|
||||
{EXPLAIN_LABEL_FILTER_CONTENT}
|
||||
</OperationExplainedBox>
|
||||
@ -32,7 +30,7 @@ export const LokiQueryBuilderExplained = React.memo<Props>(({ query }) => {
|
||||
stepNumber={2}
|
||||
queryModeller={lokiQueryModeller}
|
||||
query={visQuery}
|
||||
lang={lang}
|
||||
language={lang}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
@ -2,10 +2,9 @@ import { trim } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { CoreApp, isValidDuration, isValidGrafanaDuration, SelectableValue } from '@grafana/data';
|
||||
import { EditorField, EditorRow } from '@grafana/experimental';
|
||||
import { EditorField, EditorRow, QueryOptionGroup } from '@grafana/experimental';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
import { QueryOptionGroup } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup';
|
||||
|
||||
import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields';
|
||||
import { getLokiQueryType, isLogsQuery } from '../../queryUtils';
|
||||
|
@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { RawQuery } from '@grafana/experimental';
|
||||
import { Button, Card, useStyles2 } from '@grafana/ui';
|
||||
import { RawQuery } from 'app/plugins/datasource/prometheus/querybuilder/shared/RawQuery';
|
||||
|
||||
import logqlGrammar from '../../syntax';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
@ -31,7 +31,7 @@ export const QueryPattern = (props: Props) => {
|
||||
<div className={styles.rawQueryContainer}>
|
||||
<RawQuery
|
||||
query={lokiQueryModeller.renderQuery({ labels: [], operations: pattern.operations })}
|
||||
lang={lang}
|
||||
language={lang}
|
||||
className={styles.rawQuery}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { EditorRow, EditorFieldGroup } from '@grafana/experimental';
|
||||
import { EditorRow, EditorFieldGroup, RawQuery } from '@grafana/experimental';
|
||||
|
||||
import { RawQuery } from '../../../prometheus/querybuilder/shared/RawQuery';
|
||||
import { lokiGrammar } from '../../syntax';
|
||||
|
||||
export interface Props {
|
||||
@ -13,7 +12,7 @@ export function QueryPreview({ query }: Props) {
|
||||
return (
|
||||
<EditorRow>
|
||||
<EditorFieldGroup>
|
||||
<RawQuery query={query} lang={{ grammar: lokiGrammar, name: 'lokiql' }} />
|
||||
<RawQuery query={query} language={{ grammar: lokiGrammar, name: 'lokiql' }} />
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
);
|
||||
|
@ -3,14 +3,12 @@ import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
|
||||
import { QueryBuilderOperation, QueryBuilderOperationParamDef } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
QueryBuilderOperation,
|
||||
QueryBuilderOperationParamDef,
|
||||
} from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||
|
||||
import { createLokiDatasource } from '../../__mocks__/datasource';
|
||||
import { LokiDatasource } from '../../datasource';
|
||||
import { LokiQueryModeller } from '../LokiQueryModeller';
|
||||
import { LokiOperationId } from '../types';
|
||||
|
||||
import { UnwrapParamEditor } from './UnwrapParamEditor';
|
||||
@ -96,6 +94,9 @@ const createProps = (
|
||||
paramDef: {} as QueryBuilderOperationParamDef,
|
||||
operation: {} as QueryBuilderOperation,
|
||||
datasource: createLokiDatasource() as DataSourceApi,
|
||||
queryModeller: {
|
||||
renderQuery: jest.fn().mockReturnValue('sum_over_time({foo="bar"} | logfmt | unwrap [5m])'),
|
||||
} as unknown as LokiQueryModeller,
|
||||
};
|
||||
const props = { ...propsDefault, ...propsOverrides };
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { SelectableValue, getDefaultTimeRange, toOption } from '@grafana/data';
|
||||
import { QueryBuilderOperationParamEditorProps, VisualQueryModeller } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Select } from '@grafana/ui';
|
||||
|
||||
import { QueryBuilderOperationParamEditorProps } from '../../../prometheus/querybuilder/shared/types';
|
||||
import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation';
|
||||
import { LokiDatasource } from '../../datasource';
|
||||
import { getLogQueryFromMetricsQuery, isQueryWithError } from '../../queryUtils';
|
||||
import { extractUnwrapLabelKeysFromDataFrame } from '../../responseUtils';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
import { getOperationParamId } from '../operationUtils';
|
||||
import { LokiVisualQuery } from '../types';
|
||||
|
||||
@ -21,6 +20,7 @@ export function UnwrapParamEditor({
|
||||
query,
|
||||
datasource,
|
||||
timeRange,
|
||||
queryModeller,
|
||||
}: QueryBuilderOperationParamEditorProps) {
|
||||
const [state, setState] = useState<{
|
||||
options?: Array<SelectableValue<string>>;
|
||||
@ -34,7 +34,7 @@ export function UnwrapParamEditor({
|
||||
// This check is always true, we do it to make typescript happy
|
||||
if (datasource instanceof LokiDatasource && config.featureToggles.lokiQueryHints) {
|
||||
setState({ isLoading: true });
|
||||
const options = await loadUnwrapOptions(query, datasource, timeRange);
|
||||
const options = await loadUnwrapOptions(query, datasource, queryModeller, timeRange);
|
||||
setState({ options, isLoading: undefined });
|
||||
}
|
||||
}}
|
||||
@ -56,9 +56,10 @@ export function UnwrapParamEditor({
|
||||
async function loadUnwrapOptions(
|
||||
query: LokiVisualQuery,
|
||||
datasource: LokiDatasource,
|
||||
queryModeller: VisualQueryModeller,
|
||||
timeRange = getDefaultTimeRange()
|
||||
): Promise<Array<SelectableValue<string>>> {
|
||||
const queryExpr = lokiQueryModeller.renderQuery(query);
|
||||
const queryExpr = queryModeller.renderQuery(query);
|
||||
const logExpr = getLogQueryFromMetricsQuery(queryExpr);
|
||||
if (isQueryWithError(datasource.interpolateString(logExpr, placeHolderScopedVars))) {
|
||||
return [];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { QueryBuilderOperation, QueryBuilderOperationDef } from '../../prometheus/querybuilder/shared/types';
|
||||
import { QueryBuilderOperation, QueryBuilderOperationDefinition } from '@grafana/experimental';
|
||||
|
||||
import {
|
||||
createAggregationOperation,
|
||||
@ -10,7 +10,7 @@ import {
|
||||
labelFilterRenderer,
|
||||
pipelineRenderer,
|
||||
} from './operationUtils';
|
||||
import { getOperationDefinitions } from './operations';
|
||||
import { operationDefinitions } from './operations';
|
||||
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';
|
||||
|
||||
describe('createRangeOperation', () => {
|
||||
@ -149,7 +149,7 @@ describe('getLineFilterRenderer', () => {
|
||||
params: ['`error`'],
|
||||
};
|
||||
|
||||
const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;
|
||||
const MOCK_DEF = undefined as unknown as QueryBuilderOperationDefinition;
|
||||
|
||||
const MOCK_INNER_EXPR = '{job="grafana"}';
|
||||
|
||||
@ -178,7 +178,7 @@ describe('getLineFilterRenderer', () => {
|
||||
|
||||
describe('labelFilterRenderer', () => {
|
||||
const MOCK_MODEL = { id: '__label_filter', params: ['label', '', 'value'] };
|
||||
const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;
|
||||
const MOCK_DEF = undefined as unknown as QueryBuilderOperationDefinition;
|
||||
const MOCK_INNER_EXPR = '{job="grafana"}';
|
||||
|
||||
it.each`
|
||||
@ -220,17 +220,12 @@ describe('isConflictingFilter', () => {
|
||||
});
|
||||
|
||||
describe('pipelineRenderer', () => {
|
||||
let definitions: QueryBuilderOperationDef[];
|
||||
beforeEach(() => {
|
||||
definitions = getOperationDefinitions();
|
||||
});
|
||||
|
||||
it('correctly renders unpack expressions', () => {
|
||||
const model: QueryBuilderOperation = {
|
||||
id: LokiOperationId.Unpack,
|
||||
params: [],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Unpack);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Unpack);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
|
||||
});
|
||||
|
||||
@ -239,7 +234,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Unpack,
|
||||
params: [],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Unpack);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Unpack);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
|
||||
});
|
||||
|
||||
@ -248,7 +243,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Logfmt,
|
||||
params: [],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt');
|
||||
});
|
||||
|
||||
@ -257,7 +252,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Logfmt,
|
||||
params: [true, false, 'foo', ''],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo');
|
||||
});
|
||||
|
||||
@ -266,7 +261,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Logfmt,
|
||||
params: [true, false, 'foo', 'bar', 'baz'],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Logfmt);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | logfmt --strict foo, bar, baz');
|
||||
});
|
||||
|
||||
@ -275,7 +270,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Json,
|
||||
params: [],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Json);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json');
|
||||
});
|
||||
|
||||
@ -284,7 +279,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Json,
|
||||
params: ['foo', ''],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Json);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo');
|
||||
});
|
||||
|
||||
@ -293,7 +288,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Json,
|
||||
params: ['foo', 'bar', 'baz'],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Json);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Json);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | json foo, bar, baz');
|
||||
});
|
||||
|
||||
@ -302,7 +297,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Keep,
|
||||
params: ['foo', ''],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Keep);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Keep);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo');
|
||||
});
|
||||
|
||||
@ -311,7 +306,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Keep,
|
||||
params: ['foo', 'bar', 'baz'],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Keep);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Keep);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | keep foo, bar, baz');
|
||||
});
|
||||
|
||||
@ -320,7 +315,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Drop,
|
||||
params: ['foo', ''],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Drop);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Drop);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo');
|
||||
});
|
||||
|
||||
@ -329,7 +324,7 @@ describe('pipelineRenderer', () => {
|
||||
id: LokiOperationId.Drop,
|
||||
params: ['foo', 'bar', 'baz'],
|
||||
};
|
||||
const definition = definitions.find((def) => def.id === LokiOperationId.Drop);
|
||||
const definition = operationDefinitions.find((def) => def.id === LokiOperationId.Drop);
|
||||
expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | drop foo, bar, baz');
|
||||
});
|
||||
});
|
||||
|
@ -1,21 +1,25 @@
|
||||
import { capitalize } from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
|
||||
import { LabelParamEditor } from '../../prometheus/querybuilder/components/LabelParamEditor';
|
||||
import {
|
||||
QueryBuilderOperation,
|
||||
QueryBuilderOperationDef,
|
||||
QueryBuilderOperationDefinition,
|
||||
QueryBuilderOperationParamDef,
|
||||
QueryBuilderOperationParamValue,
|
||||
QueryWithOperations,
|
||||
VisualQuery,
|
||||
VisualQueryModeller,
|
||||
} from '../../prometheus/querybuilder/shared/types';
|
||||
} from '@grafana/experimental';
|
||||
|
||||
import { escapeLabelValueInExactSelector } from '../languageUtils';
|
||||
import { FUNCTIONS } from '../syntax';
|
||||
|
||||
import { LabelParamEditor } from './components/LabelParamEditor';
|
||||
import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
|
||||
|
||||
export function createRangeOperation(name: string, isRangeOperationWithGrouping?: boolean): QueryBuilderOperationDef {
|
||||
export function createRangeOperation(
|
||||
name: string,
|
||||
isRangeOperationWithGrouping?: boolean
|
||||
): QueryBuilderOperationDefinition {
|
||||
const params = [getRangeVectorParamDef()];
|
||||
const defaultParams = ['$__auto'];
|
||||
let paramChangedHandler = undefined;
|
||||
@ -62,11 +66,11 @@ export function createRangeOperation(name: string, isRangeOperationWithGrouping?
|
||||
};
|
||||
}
|
||||
|
||||
export function createRangeOperationWithGrouping(name: string): QueryBuilderOperationDef[] {
|
||||
export function createRangeOperationWithGrouping(name: string): QueryBuilderOperationDefinition[] {
|
||||
const rangeOperation = createRangeOperation(name, true);
|
||||
// Copy range operation params without the last param
|
||||
const params = rangeOperation.params.slice(0, -1);
|
||||
const operations: QueryBuilderOperationDef[] = [
|
||||
const operations: QueryBuilderOperationDefinition[] = [
|
||||
rangeOperation,
|
||||
{
|
||||
id: `__${name}_by`,
|
||||
@ -118,7 +122,11 @@ export function createRangeOperationWithGrouping(name: string): QueryBuilderOper
|
||||
}
|
||||
|
||||
export function getRangeAggregationWithGroupingRenderer(aggregation: string, grouping: 'by' | 'without') {
|
||||
return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function aggregationRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const restParamIndex = def.params.findIndex((param) => param.restParam);
|
||||
const params = model.params.slice(0, restParamIndex);
|
||||
const restParams = model.params.slice(restParamIndex);
|
||||
@ -133,7 +141,7 @@ export function getRangeAggregationWithGroupingRenderer(aggregation: string, gro
|
||||
|
||||
function operationWithRangeVectorRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDef,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const params = model.params ?? [];
|
||||
@ -147,7 +155,11 @@ function operationWithRangeVectorRenderer(
|
||||
return `${model.id}(${innerExpr} [${params[0] ?? '$__auto'}])`;
|
||||
}
|
||||
|
||||
export function labelFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
export function labelFilterRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const integerOperators = ['<', '<=', '>', '>='];
|
||||
|
||||
if (integerOperators.includes(String(model.params[1]))) {
|
||||
@ -161,6 +173,9 @@ export function isConflictingFilter(
|
||||
operation: QueryBuilderOperation,
|
||||
queryOperations: QueryBuilderOperation[]
|
||||
): boolean {
|
||||
if (!operation) {
|
||||
return false;
|
||||
}
|
||||
const operationIsNegative = operation.params[1].toString().startsWith('!');
|
||||
|
||||
const candidates = queryOperations.filter(
|
||||
@ -183,7 +198,11 @@ export function isConflictingFilter(
|
||||
return conflict;
|
||||
}
|
||||
|
||||
export function pipelineRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
export function pipelineRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
switch (model.id) {
|
||||
case LokiOperationId.Logfmt:
|
||||
const [strict = false, keepEmpty = false, ...labels] = model.params;
|
||||
@ -201,17 +220,17 @@ export function pipelineRenderer(model: QueryBuilderOperation, def: QueryBuilder
|
||||
}
|
||||
}
|
||||
|
||||
function isRangeVectorFunction(def: QueryBuilderOperationDef) {
|
||||
function isRangeVectorFunction(def: QueryBuilderOperationDefinition) {
|
||||
return def.category === LokiVisualQueryOperationCategory.RangeFunctions;
|
||||
}
|
||||
|
||||
function getIndexOfOrLast(
|
||||
operations: QueryBuilderOperation[],
|
||||
queryModeller: VisualQueryModeller,
|
||||
condition: (def: QueryBuilderOperationDef) => boolean
|
||||
condition: (def: QueryBuilderOperationDefinition) => boolean
|
||||
) {
|
||||
const index = operations.findIndex((x) => {
|
||||
const opDef = queryModeller.getOperationDef(x.id);
|
||||
const opDef = queryModeller.getOperationDefinition(x.id);
|
||||
if (!opDef) {
|
||||
return false;
|
||||
}
|
||||
@ -222,7 +241,7 @@ function getIndexOfOrLast(
|
||||
}
|
||||
|
||||
export function addLokiOperation(
|
||||
def: QueryBuilderOperationDef,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
query: LokiVisualQuery,
|
||||
modeller: VisualQueryModeller
|
||||
): LokiVisualQuery {
|
||||
@ -234,7 +253,7 @@ export function addLokiOperation(
|
||||
const operations = [...query.operations];
|
||||
|
||||
const existingRangeVectorFunction = operations.find((x) => {
|
||||
const opDef = modeller.getOperationDef(x.id);
|
||||
const opDef = modeller.getOperationDefinition(x.id);
|
||||
if (!opDef) {
|
||||
return false;
|
||||
}
|
||||
@ -280,7 +299,7 @@ export function addLokiOperation(
|
||||
};
|
||||
}
|
||||
|
||||
export function addNestedQueryHandler(def: QueryBuilderOperationDef, query: LokiVisualQuery): LokiVisualQuery {
|
||||
export function addNestedQueryHandler(def: QueryBuilderOperationDefinition, query: LokiVisualQuery): LokiVisualQuery {
|
||||
return {
|
||||
...query,
|
||||
binaryQueries: [
|
||||
@ -294,7 +313,11 @@ export function addNestedQueryHandler(def: QueryBuilderOperationDef, query: Loki
|
||||
}
|
||||
|
||||
export function getLineFilterRenderer(operation: string, caseInsensitive?: boolean) {
|
||||
return function lineFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function lineFilterRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const hasBackticks = model.params.some((param) => typeof param === 'string' && param.includes('`'));
|
||||
const delimiter = hasBackticks ? '"' : '`';
|
||||
let params;
|
||||
@ -324,7 +347,7 @@ export function getOperationParamId(operationId: string, paramIndex: number) {
|
||||
}
|
||||
|
||||
export function getOnLabelAddedHandler(changeToOperationId: string) {
|
||||
return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDef) {
|
||||
return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDefinition) {
|
||||
// Check if we actually have the label param. As it's optional the aggregation can have one less, which is the
|
||||
// case of just simple aggregation without label. When user adds the label it now has the same number of params
|
||||
// as its definition, and now we can change it to its `_by` variant.
|
||||
@ -361,7 +384,7 @@ export function getAggregationExplainer(aggregationName: string, mode: 'by' | 'w
|
||||
* This function will transform operations without labels to their plan aggregation operation
|
||||
*/
|
||||
export function getLastLabelRemovedHandler(changeToOperationId: string) {
|
||||
return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDef) {
|
||||
return function onParamChanged(index: number, op: QueryBuilderOperation, def: QueryBuilderOperationDefinition) {
|
||||
// If definition has more params then is defined there are no optional rest params anymore.
|
||||
// We then transform this operation into a different one
|
||||
if (op.params.length < def.params.length) {
|
||||
@ -379,7 +402,7 @@ export function getLokiOperationDisplayName(funcName: string) {
|
||||
return capitalize(funcName.replace(/_/g, ' '));
|
||||
}
|
||||
|
||||
export function defaultAddOperationHandler<T extends QueryWithOperations>(def: QueryBuilderOperationDef, query: T) {
|
||||
export function defaultAddOperationHandler<T extends VisualQuery>(def: QueryBuilderOperationDefinition, query: T) {
|
||||
const newOperation: QueryBuilderOperation = {
|
||||
id: def.id,
|
||||
params: def.defaultParams,
|
||||
@ -393,9 +416,9 @@ export function defaultAddOperationHandler<T extends QueryWithOperations>(def: Q
|
||||
|
||||
export function createAggregationOperation(
|
||||
name: string,
|
||||
overrides: Partial<QueryBuilderOperationDef> = {}
|
||||
): QueryBuilderOperationDef[] {
|
||||
const operations: QueryBuilderOperationDef[] = [
|
||||
overrides: Partial<QueryBuilderOperationDefinition> = {}
|
||||
): QueryBuilderOperationDefinition[] {
|
||||
const operations: QueryBuilderOperationDefinition[] = [
|
||||
{
|
||||
id: name,
|
||||
name: getLokiOperationDisplayName(name),
|
||||
@ -466,12 +489,20 @@ export function createAggregationOperation(
|
||||
}
|
||||
|
||||
function getAggregationWithoutRenderer(aggregation: string) {
|
||||
return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function aggregationRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
return `${aggregation} without(${model.params.join(', ')}) (${innerExpr})`;
|
||||
};
|
||||
}
|
||||
|
||||
export function functionRendererLeft(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
export function functionRendererLeft(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const params = renderParams(model, def, innerExpr);
|
||||
const str = model.id + '(';
|
||||
|
||||
@ -482,7 +513,7 @@ export function functionRendererLeft(model: QueryBuilderOperation, def: QueryBui
|
||||
return str + params.join(', ') + ')';
|
||||
}
|
||||
|
||||
function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDefinition, innerExpr: string) {
|
||||
return (model.params ?? []).map((value, index) => {
|
||||
const paramDef = def.params[index];
|
||||
if (paramDef.type === 'string') {
|
||||
@ -494,7 +525,11 @@ function renderParams(model: QueryBuilderOperation, def: QueryBuilderOperationDe
|
||||
}
|
||||
|
||||
function getAggregationByRenderer(aggregation: string) {
|
||||
return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function aggregationRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
return `${aggregation} by(${model.params.join(', ')}) (${innerExpr})`;
|
||||
};
|
||||
}
|
||||
@ -502,8 +537,8 @@ function getAggregationByRenderer(aggregation: string) {
|
||||
export function createAggregationOperationWithParam(
|
||||
name: string,
|
||||
paramsDef: { params: QueryBuilderOperationParamDef[]; defaultParams: QueryBuilderOperationParamValue[] },
|
||||
overrides: Partial<QueryBuilderOperationDef> = {}
|
||||
): QueryBuilderOperationDef[] {
|
||||
overrides: Partial<QueryBuilderOperationDefinition> = {}
|
||||
): QueryBuilderOperationDefinition[] {
|
||||
const operations = createAggregationOperation(name, overrides);
|
||||
operations[0].params.unshift(...paramsDef.params);
|
||||
operations[1].params.unshift(...paramsDef.params);
|
||||
@ -517,7 +552,11 @@ export function createAggregationOperationWithParam(
|
||||
}
|
||||
|
||||
function getAggregationByRendererWithParameter(aggregation: string) {
|
||||
return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
|
||||
return function aggregationRenderer(
|
||||
model: QueryBuilderOperation,
|
||||
def: QueryBuilderOperationDefinition,
|
||||
innerExpr: string
|
||||
) {
|
||||
const restParamIndex = def.params.findIndex((param) => param.restParam);
|
||||
const params = model.params.slice(0, restParamIndex);
|
||||
const restParams = model.params.slice(restParamIndex);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { explainOperator, getOperationDefinitions } from './operations';
|
||||
import { explainOperator, operationDefinitions } from './operations';
|
||||
import { LokiOperationId } from './types';
|
||||
|
||||
const undocumentedOperationsIds: string[] = [
|
||||
@ -21,8 +21,7 @@ describe('explainOperator', () => {
|
||||
let operations = [];
|
||||
let undocumentedOperations = [];
|
||||
|
||||
const definitions = getOperationDefinitions();
|
||||
for (const definition of definitions) {
|
||||
for (const definition of operationDefinitions) {
|
||||
if (!undocumentedOperationsIds.includes(definition.id)) {
|
||||
operations.push(definition.id);
|
||||
} else {
|
||||
@ -31,7 +30,7 @@ describe('explainOperator', () => {
|
||||
}
|
||||
|
||||
test('Resolves operation definitions', () => {
|
||||
expect(definitions.length).toBeGreaterThan(0);
|
||||
expect(operationDefinitions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test.each(operations)('Returns docs for the %s operation', (operation) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { QueryBuilderOperationDef, QueryBuilderOperationParamValue } from '../../prometheus/querybuilder/shared/types';
|
||||
import { QueryBuilderOperationDefinition, QueryBuilderOperationParamValue } from '@grafana/experimental';
|
||||
|
||||
import { binaryScalarOperations } from './binaryScalarOperations';
|
||||
import { UnwrapParamEditor } from './components/UnwrapParamEditor';
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from './operationUtils';
|
||||
import { LokiOperationId, LokiOperationOrder, lokiOperators, LokiVisualQueryOperationCategory } from './types';
|
||||
|
||||
export function getOperationDefinitions(): QueryBuilderOperationDef[] {
|
||||
function getOperationDefinitions(): QueryBuilderOperationDefinition[] {
|
||||
const aggregations = [
|
||||
LokiOperationId.Sum,
|
||||
LokiOperationId.Min,
|
||||
@ -66,7 +66,7 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
|
||||
...createRangeOperationWithGrouping(LokiOperationId.QuantileOverTime),
|
||||
];
|
||||
|
||||
const list: QueryBuilderOperationDef[] = [
|
||||
const list: QueryBuilderOperationDefinition[] = [
|
||||
...aggregations,
|
||||
...aggregationsWithParam,
|
||||
...rangeOperations,
|
||||
@ -581,14 +581,14 @@ Example: \`\`error_level=\`level\` \`\`
|
||||
}
|
||||
|
||||
// Keeping a local copy as an optimization measure.
|
||||
const definitions = getOperationDefinitions();
|
||||
export const operationDefinitions = getOperationDefinitions();
|
||||
|
||||
/**
|
||||
* Given an operator, return the corresponding explain.
|
||||
* For usage within the Query Editor.
|
||||
*/
|
||||
export function explainOperator(id: LokiOperationId | string): string {
|
||||
const definition = definitions.find((operation) => operation.id === id);
|
||||
const definition = operationDefinitions.find((operation) => operation.id === id);
|
||||
|
||||
const explain = definition?.explainHandler?.({ id: '', params: ['<value>'] }) || '';
|
||||
|
||||
@ -596,11 +596,14 @@ export function explainOperator(id: LokiOperationId | string): string {
|
||||
return explain.replace(/\[(.*)\]\(.*\)/g, '$1');
|
||||
}
|
||||
|
||||
export function getDefinitionById(id: string): QueryBuilderOperationDef | undefined {
|
||||
return definitions.find((x) => x.id === id);
|
||||
export function getDefinitionById(id: string): QueryBuilderOperationDefinition | undefined {
|
||||
return operationDefinitions.find((x) => x.id === id);
|
||||
}
|
||||
|
||||
export function checkParamsAreValid(def: QueryBuilderOperationDef, params: QueryBuilderOperationParamValue[]): boolean {
|
||||
export function checkParamsAreValid(
|
||||
def: QueryBuilderOperationDefinition,
|
||||
params: QueryBuilderOperationParamValue[]
|
||||
): boolean {
|
||||
// For now we only check if the operation has all the required params.
|
||||
if (params.length < def.params.filter((param) => !param.optional).length) {
|
||||
return false;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { SyntaxNode } from '@lezer/common';
|
||||
|
||||
import { QueryBuilderLabelFilter, QueryBuilderOperation, QueryBuilderOperationParamValue } from '@grafana/experimental';
|
||||
import {
|
||||
And,
|
||||
BinOpExpr,
|
||||
@ -54,12 +55,6 @@ import {
|
||||
OrFilter,
|
||||
} from '@grafana/lezer-logql';
|
||||
|
||||
import {
|
||||
QueryBuilderLabelFilter,
|
||||
QueryBuilderOperation,
|
||||
QueryBuilderOperationParamValue,
|
||||
} from '../../prometheus/querybuilder/shared/types';
|
||||
|
||||
import { binaryScalarDefs } from './binaryScalarOperations';
|
||||
import { checkParamsAreValid, getDefinitionById } from './operations';
|
||||
import {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SyntaxNode, TreeCursor } from '@lezer/common';
|
||||
|
||||
import { QueryBuilderOperation, QueryBuilderOperationParamValue } from '../../prometheus/querybuilder/shared/types';
|
||||
import { QueryBuilderOperation, QueryBuilderOperationParamValue } from '@grafana/experimental';
|
||||
|
||||
// Although 0 isn't explicitly provided in the @grafana/lezer-logql library as the error node ID, it does appear to be the ID of error nodes within lezer.
|
||||
export const ErrorId = 0;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
|
||||
import { QueryEditorMode } from '@grafana/experimental';
|
||||
|
||||
import { changeEditorMode, getQueryWithDefaults } from './state';
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
|
||||
import { QueryEditorMode } from '@grafana/experimental';
|
||||
|
||||
import { LokiQuery, LokiQueryType } from '../types';
|
||||
|
||||
const queryEditorModeDefaultLocalStorageKey = 'LokiQueryEditorModeDefault';
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { VisualQueryBinary } from '../../prometheus/querybuilder/shared/LokiAndPromQueryModellerBase';
|
||||
import { QueryBuilderLabelFilter, QueryBuilderOperation } from '../../prometheus/querybuilder/shared/types';
|
||||
import {
|
||||
VisualQueryBinary,
|
||||
QueryBuilderLabelFilter,
|
||||
QueryBuilderOperation,
|
||||
BINARY_OPERATIONS_KEY,
|
||||
} from '@grafana/experimental';
|
||||
|
||||
/**
|
||||
* Visual query model
|
||||
@ -29,7 +33,7 @@ export enum LokiVisualQueryOperationCategory {
|
||||
Formats = 'Formats',
|
||||
LineFilters = 'Line filters',
|
||||
LabelFilters = 'Label filters',
|
||||
BinaryOps = 'Binary operations',
|
||||
BinaryOps = BINARY_OPERATIONS_KEY,
|
||||
}
|
||||
|
||||
export enum LokiOperationId {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { DashboardLoadedEvent, DataQueryRequest, dateTime } from '@grafana/data';
|
||||
import { QueryEditorMode } from '@grafana/experimental';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { QueryEditorMode } from '../prometheus/querybuilder/shared/types';
|
||||
|
||||
import pluginJson from './plugin.json';
|
||||
import { partitionTimeRange } from './querySplitting';
|
||||
import { onDashboardLoadedHandler, trackGroupedQueries, trackQuery } from './tracking';
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { CoreApp, DashboardLoadedEvent, DataQueryRequest, DataQueryResponse } from '@grafana/data';
|
||||
import { QueryEditorMode } from '@grafana/experimental';
|
||||
import { reportInteraction, config } from '@grafana/runtime';
|
||||
|
||||
import { QueryEditorMode } from '../prometheus/querybuilder/shared/types';
|
||||
|
||||
import {
|
||||
REF_ID_STARTER_ANNOTATION,
|
||||
REF_ID_DATA_SAMPLES,
|
||||
|
Loading…
Reference in New Issue
Block a user