TemplateVariables: Introduces $__searchFilter to Query Variables (#19858)

* WIP: Initial hardcoded version

* Feature: Introduces SearchFiltering to Graphite

* Feature: Adds searchFiltering to MySql

* Tests: Adds tests to Graphite and MySql

* Feature: Adds $__searchFilter to TestData

* Refactor: Adds searchFilter to Postgres and extracts function

* Tests: Adds tests to variable

* Refactor: Adds debounce and lodash import optimization

* Docs: Adds documentation

* Refactor: Removes unused function and fixes typo

* Docs: Updates docs

* Fixed issue with UI not updating when no  was used due to async func and no .apply in the non lazy path
This commit is contained in:
Hugo Häggmark
2019-10-18 11:40:08 +02:00
committed by Torkel Ödegaard
parent c674fa1d79
commit cb0e80e7b9
18 changed files with 601 additions and 59 deletions

View File

@@ -1,5 +1,5 @@
import _ from 'lodash';
import { Variable, containsVariable, assignModelProperties, variableTypes } from './variable';
import { assignModelProperties, containsVariable, Variable, variableTypes } from './variable';
import { stringToJsRegex } from '@grafana/data';
import DatasourceSrv from '../plugins/datasource_srv';
import { TemplateSrv } from './template_srv';
@@ -62,6 +62,7 @@ export class QueryVariable implements Variable {
) {
// copy model properties to this instance
assignModelProperties(this, model, this.defaults);
this.updateOptionsFromMetricFindQuery.bind(this);
}
getSaveModel() {
@@ -91,10 +92,10 @@ export class QueryVariable implements Variable {
return this.current.value;
}
updateOptions() {
updateOptions(searchFilter?: string) {
return this.datasourceSrv
.get(this.datasource)
.then(this.updateOptionsFromMetricFindQuery.bind(this))
.then(ds => this.updateOptionsFromMetricFindQuery(ds, searchFilter))
.then(this.updateTags.bind(this))
.then(this.variableSrv.validateVariableSelectionState.bind(this.variableSrv, this));
}
@@ -126,8 +127,8 @@ export class QueryVariable implements Variable {
});
}
updateOptionsFromMetricFindQuery(datasource: any) {
return this.metricFindQuery(datasource, this.query).then((results: any) => {
updateOptionsFromMetricFindQuery(datasource: any, searchFilter?: string) {
return this.metricFindQuery(datasource, this.query, searchFilter).then((results: any) => {
this.options = this.metricNamesToVariableValues(results);
if (this.includeAll) {
this.addAllOption();
@@ -139,8 +140,8 @@ export class QueryVariable implements Variable {
});
}
metricFindQuery(datasource: any, query: string) {
const options: any = { range: undefined, variable: this };
metricFindQuery(datasource: any, query: string, searchFilter?: string) {
const options: any = { range: undefined, variable: this, searchFilter };
if (this.refresh === 2) {
options.range = this.timeSrv.timeRange();

View File

@@ -1,4 +1,10 @@
import { containsVariable, assignModelProperties } from '../variable';
import {
assignModelProperties,
containsSearchFilter,
containsVariable,
interpolateSearchFilter,
SEARCH_FILTER_VARIABLE,
} from '../variable';
describe('containsVariable', () => {
describe('when checking if a string contains a variable', () => {
@@ -68,3 +74,104 @@ describe('assignModelProperties', () => {
expect(target.propC).toBe(10);
});
});
describe('containsSearchFilter', () => {
describe('when called without query', () => {
it('then it should return false', () => {
const result = containsSearchFilter(null);
expect(result).toBe(false);
});
});
describe(`when called with a query without ${SEARCH_FILTER_VARIABLE}`, () => {
it('then it should return false', () => {
const result = containsSearchFilter('$app.*');
expect(result).toBe(false);
});
});
describe(`when called with a query with ${SEARCH_FILTER_VARIABLE}`, () => {
it('then it should return false', () => {
const result = containsSearchFilter(`$app.${SEARCH_FILTER_VARIABLE}`);
expect(result).toBe(true);
});
});
});
describe('interpolateSearchFilter', () => {
describe('when called with a query without ${SEARCH_FILTER_VARIABLE}', () => {
it('then it should return query', () => {
const query = '$app.*';
const options = { searchFilter: 'filter' };
const wildcardChar = '*';
const quoteLiteral = false;
const result = interpolateSearchFilter({
query,
options,
wildcardChar,
quoteLiteral,
});
expect(result).toEqual(query);
});
});
describe(`when called with a query with ${SEARCH_FILTER_VARIABLE}`, () => {
const query = `$app.${SEARCH_FILTER_VARIABLE}`;
describe('and no searchFilter is given', () => {
it(`then ${SEARCH_FILTER_VARIABLE} should be replaced by wildchar character`, () => {
const options = {};
const wildcardChar = '*';
const quoteLiteral = false;
const result = interpolateSearchFilter({
query,
options,
wildcardChar,
quoteLiteral,
});
expect(result).toEqual(`$app.*`);
});
});
describe('and searchFilter is given', () => {
const options = { searchFilter: 'filter' };
it(`then ${SEARCH_FILTER_VARIABLE} should be replaced with searchfilter and wildchar character`, () => {
const wildcardChar = '*';
const quoteLiteral = false;
const result = interpolateSearchFilter({
query,
options,
wildcardChar,
quoteLiteral,
});
expect(result).toEqual(`$app.filter*`);
});
describe(`and quoteLiteral is used`, () => {
it(`then the literal should be quoted`, () => {
const wildcardChar = '*';
const quoteLiteral = true;
const result = interpolateSearchFilter({
query,
options,
wildcardChar,
quoteLiteral,
});
expect(result).toEqual(`$app.'filter*'`);
});
});
});
});
});

View File

@@ -15,9 +15,36 @@ export const variableRegexExec = (variableString: string) => {
return variableRegex.exec(variableString);
};
export const SEARCH_FILTER_VARIABLE = '$__searchFilter';
export const containsSearchFilter = (query: string): boolean =>
query ? query.indexOf(SEARCH_FILTER_VARIABLE) !== -1 : false;
export interface InterpolateSearchFilterOptions {
query: string;
options: any;
wildcardChar: string;
quoteLiteral: boolean;
}
export const interpolateSearchFilter = (args: InterpolateSearchFilterOptions): string => {
const { query, wildcardChar, quoteLiteral } = args;
let { options } = args;
if (!containsSearchFilter(query)) {
return query;
}
options = options || {};
const filter = options.searchFilter ? `${options.searchFilter}${wildcardChar}` : `${wildcardChar}`;
const replaceValue = quoteLiteral ? `'${filter}'` : filter;
return query.replace(SEARCH_FILTER_VARIABLE, replaceValue);
};
export interface Variable {
setValue(option: any): any;
updateOptions(): any;
updateOptions(searchFilter?: string): any;
dependsOn(variable: any): any;
setValueFromUrl(urlValue: any): any;
getValueForUrl(): any;