diff --git a/public/app/plugins/datasource/loki/components/LokiContextUi.tsx b/public/app/plugins/datasource/loki/components/LokiContextUi.tsx
index 6b7dda28026..0b03fd32316 100644
--- a/public/app/plugins/datasource/loki/components/LokiContextUi.tsx
+++ b/public/app/plugins/datasource/loki/components/LokiContextUi.tsx
@@ -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) {
{initialized ? (
<>
-
+
diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx
index 4264c1d24fb..c2720a046bf 100644
--- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx
+++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx
@@ -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';
diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
index 5b2eb4a5e89..d0c9f84529a 100644
--- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
+++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
@@ -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';
diff --git a/public/app/plugins/datasource/loki/modifyQuery.ts b/public/app/plugins/datasource/loki/modifyQuery.ts
index db28c7a4ba2..b18887b4c13 100644
--- a/public/app/plugins/datasource/loki/modifyQuery.ts
+++ b/public/app/plugins/datasource/loki/modifyQuery.ts
@@ -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';
diff --git a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts
index fa621ee8d6a..7c7b7ac23bc 100644
--- a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts
+++ b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.test.ts
@@ -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);
});
diff --git a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts
index 2a95304c736..703d4c50f4f 100644
--- a/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts
+++ b/public/app/plugins/datasource/loki/querybuilder/LokiQueryModeller.ts
@@ -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, '
');
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>) {
+ if (binaryQueries) {
+ for (const binQuery of binaryQueries) {
+ queryString = `${this.renderBinaryQuery(queryString, binQuery)}`;
+ }
+ }
+ return queryString;
+ }
+
+ private renderBinaryQuery(leftOperand: string, binaryQuery: VisualQueryBinary) {
+ 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[] {
diff --git a/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts b/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts
index cd62b2e8f49..a80f803eeab 100644
--- a/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts
+++ b/public/app/plugins/datasource/loki/querybuilder/binaryScalarOperations.ts
@@ -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) {
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.tsx
new file mode 100644
index 00000000000..ea9186ebdd7
--- /dev/null
+++ b/public/app/plugins/datasource/loki/querybuilder/components/LabelParamEditor.tsx
@@ -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 (
+