mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 23:52:20 +08:00
AdHocFilters: Add support for new isOneOf
multi value operator (#91837)
* handle oneOf operator in prometheus * use new supportsMultiValueOperators * remap oneOf to regex in prometheus datasource * Remap one of operators for scope filters * use plugin.json property instead of feature toggle * optional chaining * fix unit tests * use getInstanceSettings * update to latest scenes * fix unit tests --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
@ -308,6 +308,10 @@
|
||||
"type": "boolean",
|
||||
"description": "For data source plugins, if the plugin supports metric queries. Used to enable the plugin in the panel editor."
|
||||
},
|
||||
"multiValueFilterOperators": {
|
||||
"type": "boolean",
|
||||
"description": "For data source plugins, if the plugin supports multi value operators in adhoc filters."
|
||||
},
|
||||
"pascalName": {
|
||||
"type": "string",
|
||||
"description": "[internal only] The PascalCase name for the plugin. Used for creating machine-friendly identifiers, typically in code generation. If not provided, defaults to name, but title-cased and sanitized (only alphabetical characters allowed).",
|
||||
|
@ -268,7 +268,7 @@
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/saga-icons": "workspace:*",
|
||||
"@grafana/scenes": "^5.10.1",
|
||||
"@grafana/scenes": "^5.11.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
@ -142,6 +142,7 @@ export interface DataSourcePluginMeta<T extends KeyValue = {}> extends PluginMet
|
||||
unlicensed?: boolean;
|
||||
backend?: boolean;
|
||||
isBackend?: boolean;
|
||||
multiValueFilterOperators?: boolean;
|
||||
}
|
||||
|
||||
interface PluginMetaQueryOptions {
|
||||
|
@ -53,6 +53,7 @@ export interface AdHocVariableFilter {
|
||||
key: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
values?: string[];
|
||||
/** @deprecated */
|
||||
condition?: string;
|
||||
}
|
||||
|
@ -603,7 +603,7 @@ export class PrometheusDatasource
|
||||
return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k }));
|
||||
}
|
||||
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map(remapOneOf).map((f) => ({
|
||||
label: f.key,
|
||||
value: f.value,
|
||||
op: f.operator,
|
||||
@ -620,7 +620,7 @@ export class PrometheusDatasource
|
||||
|
||||
// By implementing getTagKeys and getTagValues we add ad-hoc filters functionality
|
||||
async getTagValues(options: DataSourceGetTagValuesOptions<PromQuery>) {
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map(remapOneOf).map((f) => ({
|
||||
label: f.key,
|
||||
value: f.value,
|
||||
op: f.operator,
|
||||
@ -822,7 +822,7 @@ export class PrometheusDatasource
|
||||
return [];
|
||||
}
|
||||
|
||||
return filters.map((f) => ({
|
||||
return filters.map(remapOneOf).map((f) => ({
|
||||
...f,
|
||||
value: this.templateSrv.replace(f.value, {}, this.interpolateQueryExpr),
|
||||
operator: scopeFilterOperatorMap[f.operator],
|
||||
@ -834,7 +834,7 @@ export class PrometheusDatasource
|
||||
return expr;
|
||||
}
|
||||
|
||||
const finalQuery = filters.reduce((acc, filter) => {
|
||||
const finalQuery = filters.map(remapOneOf).reduce((acc, filter) => {
|
||||
const { key, operator } = filter;
|
||||
let { value } = filter;
|
||||
if (operator === '=~' || operator === '!~') {
|
||||
@ -1001,3 +1001,19 @@ export function prometheusRegularEscape<T>(value: T) {
|
||||
export function prometheusSpecialRegexEscape<T>(value: T) {
|
||||
return typeof value === 'string' ? value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]\'+?.()|]/g, '\\\\$&') : value;
|
||||
}
|
||||
|
||||
export function remapOneOf(filter: AdHocVariableFilter) {
|
||||
let { operator, value, values } = filter;
|
||||
if (operator === '=|') {
|
||||
operator = '=~';
|
||||
value = values?.map(prometheusRegularEscape).join('|') ?? '';
|
||||
} else if (operator === '!=|') {
|
||||
operator = '!~';
|
||||
value = values?.map(prometheusRegularEscape).join('|') ?? '';
|
||||
}
|
||||
return {
|
||||
...filter,
|
||||
operator,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
@ -454,6 +454,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
Module: plugin.Module,
|
||||
BaseURL: plugin.BaseURL,
|
||||
Angular: plugin.Angular,
|
||||
MultiValueFilterOperators: plugin.MultiValueFilterOperators,
|
||||
}
|
||||
|
||||
if ds.JsonData == nil {
|
||||
|
@ -171,13 +171,11 @@ type Signature struct {
|
||||
|
||||
type PluginMetaDTO struct {
|
||||
JSONData
|
||||
|
||||
Signature SignatureStatus `json:"signature"`
|
||||
|
||||
Module string `json:"module"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
|
||||
Angular AngularMeta `json:"angular"`
|
||||
MultiValueFilterOperators bool `json:"multiValueFilterOperators"`
|
||||
}
|
||||
|
||||
type DataSourceDTO struct {
|
||||
|
@ -124,6 +124,7 @@ type JSONData struct {
|
||||
Mixed bool `json:"mixed,omitempty"`
|
||||
Streaming bool `json:"streaming"`
|
||||
SDK bool `json:"sdk,omitempty"`
|
||||
MultiValueFilterOperators bool `json:"multiValueFilterOperators,omitempty"`
|
||||
|
||||
// Backend (Datasource + Renderer + SecretsManager)
|
||||
Executable string `json:"executable,omitempty"`
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AnnotationChangeEvent, AnnotationEventUIModel, CoreApp, DataFrame } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { AdHocFiltersVariable, dataLayers, sceneGraph, sceneUtils, VizPanel } from '@grafana/scenes';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { AdHocFilterItem, PanelContext } from '@grafana/ui';
|
||||
@ -160,6 +161,7 @@ export function getAdHocFilterVariableFor(scene: DashboardScene, ds: DataSourceR
|
||||
const newVariable = new AdHocFiltersVariable({
|
||||
name: 'Filters',
|
||||
datasource: ds,
|
||||
supportsMultiValueOperators: Boolean(getDataSourceSrv().getInstanceSettings(ds)?.meta.multiValueFilterOperators),
|
||||
useQueriesAsFilterForOptions: true,
|
||||
});
|
||||
|
||||
|
@ -136,6 +136,8 @@ jest.mock('@grafana/runtime', () => ({
|
||||
toDataQuery: (q: StandardVariableQuery) => q,
|
||||
},
|
||||
}),
|
||||
// mock getInstanceSettings()
|
||||
getInstanceSettings: jest.fn(),
|
||||
}),
|
||||
getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => {
|
||||
return runRequestMock(ds, request);
|
||||
|
@ -28,6 +28,7 @@ export function AdHocFiltersVariableEditor(props: AdHocFiltersVariableEditorProp
|
||||
|
||||
variable.setState({
|
||||
datasource: dsRef,
|
||||
supportsMultiValueOperators: ds.meta.multiValueFilterOperators,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -26,6 +26,14 @@ import { NEW_LINK } from '../settings/links/utils';
|
||||
|
||||
import { createSceneVariableFromVariableModel, createVariablesForSnapshot } from './variables';
|
||||
|
||||
// mock getDataSourceSrv.getInstanceSettings()
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => ({
|
||||
getInstanceSettings: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('when creating variables objects', () => {
|
||||
it('should migrate custom variable', () => {
|
||||
const variable: CustomVariableModel = {
|
||||
@ -425,6 +433,7 @@ describe('when creating variables objects', () => {
|
||||
datasource: { uid: 'gdev-prometheus', type: 'prometheus' },
|
||||
applyMode: 'auto',
|
||||
useQueriesAsFilterForOptions: true,
|
||||
supportsMultiValueOperators: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -508,6 +517,7 @@ describe('when creating variables objects', () => {
|
||||
},
|
||||
],
|
||||
useQueriesAsFilterForOptions: true,
|
||||
supportsMultiValueOperators: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TypedVariableModel } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
import {
|
||||
AdHocFiltersVariable,
|
||||
ConstantVariable,
|
||||
@ -56,6 +56,9 @@ export function createVariablesForSnapshot(oldModel: DashboardModel) {
|
||||
baseFilters: v.baseFilters ?? [],
|
||||
defaultKeys: v.defaultKeys,
|
||||
useQueriesAsFilterForOptions: true,
|
||||
supportsMultiValueOperators: Boolean(
|
||||
getDataSourceSrv().getInstanceSettings(v.datasource)?.meta.multiValueFilterOperators
|
||||
),
|
||||
});
|
||||
}
|
||||
// for other variable types we are using the SnapshotVariable
|
||||
@ -133,6 +136,9 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
||||
baseFilters: variable.baseFilters ?? [],
|
||||
defaultKeys: variable.defaultKeys,
|
||||
useQueriesAsFilterForOptions: true,
|
||||
supportsMultiValueOperators: Boolean(
|
||||
getDataSourceSrv().getInstanceSettings(variable.datasource)?.meta.multiValueFilterOperators
|
||||
),
|
||||
});
|
||||
}
|
||||
if (variable.type === 'custom') {
|
||||
|
@ -262,6 +262,8 @@ function getVariableSet(initialDS?: string, metric?: string, initialFilters?: Ad
|
||||
layout: 'vertical',
|
||||
filters: initialFilters ?? [],
|
||||
baseFilters: getBaseFiltersForMetric(metric),
|
||||
// since we only support prometheus datasources, this is always true
|
||||
supportsMultiValueOperators: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -89,6 +89,7 @@
|
||||
"queryOptions": {
|
||||
"minInterval": true
|
||||
},
|
||||
"multiValueFilterOperators": true,
|
||||
"info": {
|
||||
"description": "Open source time series database & alerting",
|
||||
"author": {
|
||||
|
10
yarn.lock
10
yarn.lock
@ -3918,9 +3918,9 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes@npm:^5.10.1":
|
||||
version: 5.10.2
|
||||
resolution: "@grafana/scenes@npm:5.10.2"
|
||||
"@grafana/scenes@npm:^5.11.0":
|
||||
version: 5.11.0
|
||||
resolution: "@grafana/scenes@npm:5.11.0"
|
||||
dependencies:
|
||||
"@grafana/e2e-selectors": "npm:^11.0.0"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.14"
|
||||
@ -3935,7 +3935,7 @@ __metadata:
|
||||
"@grafana/ui": ">=10.4"
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
checksum: 10/f7409cc8b7d3687baba7d5af307fd3f836579f20a71e0a782841ba7031d786a2b039757d0e89822af650e825c6b6102571b524a7f24179002da02d2eeaa3cc8b
|
||||
checksum: 10/4b56c0c831468651f75992979820541c07d3e9cfcb2547c58aed647a60ad02bd8d81b68d233c2ecc401f5b6400e7c29cbe9408f6f5e374bb30161d47ab0f08e2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -18495,7 +18495,7 @@ __metadata:
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/saga-icons": "workspace:*"
|
||||
"@grafana/scenes": "npm:^5.10.1"
|
||||
"@grafana/scenes": "npm:^5.11.0"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
|
Reference in New Issue
Block a user