Transformations: Add time filtering to filter by value (#101591)

* WIP - time filtering

* Replace variable test

* Change tests

* Validator is already tested

* Change test to match evaluation

* Add line to docs

* Revert "Add line to docs"

This reverts commit 783f247c33e854e00a72a42c5d04eee3aa923a5a.

* Put transformations docs update in the right place, cannot build without an update, WIP

* Run build

* Use regex test and rewind

* Does this help

* make config optional
This commit is contained in:
Kristina
2025-06-16 14:27:34 -05:00
committed by GitHub
parent fc97b0e6b4
commit c00caa2fb2
7 changed files with 31 additions and 16 deletions

View File

@ -461,13 +461,13 @@ The available conditions for string fields are:
- **Contains substring** - Match if the value contains the specified substring (case insensitive). - **Contains substring** - Match if the value contains the specified substring (case insensitive).
- **Does not contain substring** - Match if the value doesn't contain the specified substring (case insensitive). - **Does not contain substring** - Match if the value doesn't contain the specified substring (case insensitive).
The available conditions for number fields are: The available conditions for number and time fields are:
- **Greater** - Match if the value is greater than the specified value. - **Greater** - Match if the value is greater than the specified value.
- **Lower** - Match if the value is lower than the specified value. - **Lower** - Match if the value is lower than the specified value.
- **Greater or equal** - Match if the value is greater or equal. - **Greater or equal** - Match if the value is greater or equal.
- **Lower or equal** - Match if the value is lower or equal. - **Lower or equal** - Match if the value is lower or equal.
- **Range** - Match a range between a specified minimum and maximum, min and max included. - **Range** - Match a range between a specified minimum and maximum, min and max included. A time field will pre-populate with variables to filter by selected time.
Consider the following dataset: Consider the following dataset:

View File

@ -14,14 +14,25 @@ const isBetweenValueMatcher: ValueMatcherInfo<RangeValueMatcherOptions> = {
if (isNaN(value)) { if (isNaN(value)) {
return false; return false;
} }
return value > options.from && value < options.to;
// if it is a time, it is interpolated as a string, so convert before comparing
const fromVal = typeof options.from !== 'number' ? parseInt(options.from, 10) : options.from;
const toVal = typeof options.to !== 'number' ? parseInt(options.to, 10) : options.to;
return value > fromVal && value < toVal;
}; };
}, },
getOptionsDisplayText: (options) => { getOptionsDisplayText: (options) => {
return `Matches all rows where field value is between ${options.from} and ${options.to}.`; return `Matches all rows where field value is between ${options.from} and ${options.to}.`;
}, },
isApplicable: (field) => field.type === FieldType.number, isApplicable: (field) => field.type === FieldType.number || field.type === FieldType.time,
getDefaultOptions: () => ({ from: 0, to: 100 }), getDefaultOptions: (field) => {
if (field.type === FieldType.time) {
return { from: '$__from', to: '$__to' };
} else {
return { from: 0, to: 100 };
}
},
}; };
export const getRangeValueMatchers = (): ValueMatcherInfo[] => [isBetweenValueMatcher]; export const getRangeValueMatchers = (): ValueMatcherInfo[] => [isBetweenValueMatcher];

View File

@ -18,4 +18,4 @@ export const updateConfig = (update: Partial<GrafanaBootConfig>) => {
}; };
// The `enable_alpha` flag is not exposed directly, this is equivalent // The `enable_alpha` flag is not exposed directly, this is equivalent
export const hasAlphaPanels = Boolean(config.panels?.debug?.state === PluginState.alpha); export const hasAlphaPanels = Boolean(config?.panels?.debug?.state === PluginState.alpha);

View File

@ -70,7 +70,7 @@ export const expressionTypes: Array<SelectableValue<ExpressionQueryType>> = [
}, },
].filter((expr) => { ].filter((expr) => {
if (expr.value === ExpressionQueryType.sql) { if (expr.value === ExpressionQueryType.sql) {
return config.featureToggles?.sqlExpressions; return config?.featureToggles?.sqlExpressions;
} }
return true; return true;
}); });

View File

@ -380,13 +380,13 @@ The available conditions for string fields are:
- **Contains substring** - Match if the value contains the specified substring (case insensitive). - **Contains substring** - Match if the value contains the specified substring (case insensitive).
- **Does not contain substring** - Match if the value doesn't contain the specified substring (case insensitive). - **Does not contain substring** - Match if the value doesn't contain the specified substring (case insensitive).
The available conditions for number fields are: The available conditions for number and time fields are:
- **Greater** - Match if the value is greater than the specified value. - **Greater** - Match if the value is greater than the specified value.
- **Lower** - Match if the value is lower than the specified value. - **Lower** - Match if the value is lower than the specified value.
- **Greater or equal** - Match if the value is greater or equal. - **Greater or equal** - Match if the value is greater or equal.
- **Lower or equal** - Match if the value is lower or equal. - **Lower or equal** - Match if the value is lower or equal.
- **Range** - Match a range between a specified minimum and maximum, min and max included. - **Range** - Match a range between a specified minimum and maximum, min and max included. A time field will pre-populate with variables to filter by selected time.
Consider the following dataset: Consider the following dataset:

View File

@ -27,19 +27,19 @@ describe('validator', () => {
expect(numberOrVariableValidator('1')).toBe(true); expect(numberOrVariableValidator('1')).toBe(true);
}); });
it('validats a string that is a negative integer', () => { it('validates a string that is a negative integer', () => {
expect(numberOrVariableValidator('-1')).toBe(true); expect(numberOrVariableValidator('-1')).toBe(true);
}); });
it('validats a string that is zero', () => { it('validates a string that is zero', () => {
expect(numberOrVariableValidator('0')).toBe(true); expect(numberOrVariableValidator('0')).toBe(true);
}); });
it('validats a string that is a float', () => { it('validates a string that is a float', () => {
expect(numberOrVariableValidator('1.2')).toBe(true); expect(numberOrVariableValidator('1.2')).toBe(true);
}); });
it('validats a string that is a negative float', () => { it('validates a string that is a negative float', () => {
expect(numberOrVariableValidator('-1.2')).toBe(true); expect(numberOrVariableValidator('-1.2')).toBe(true);
}); });
@ -51,8 +51,8 @@ describe('validator', () => {
expect(numberOrVariableValidator('$foo')).toBe(true); expect(numberOrVariableValidator('$foo')).toBe(true);
}); });
it('fails a string that has multiple variables', () => { it('validates a string that has multiple variables', () => {
expect(numberOrVariableValidator('$foo$asd')).toBe(false); expect(numberOrVariableValidator('$foo$asd')).toBe(true);
}); });
}); });

View File

@ -12,6 +12,8 @@ import {
import { t } from '@grafana/i18n'; import { t } from '@grafana/i18n';
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { variableRegex } from '../variables/utils';
export const getAllFieldNamesFromDataFrames = (frames: DataFrame[], withBaseFieldNames = false) => { export const getAllFieldNamesFromDataFrames = (frames: DataFrame[], withBaseFieldNames = false) => {
// get full names // get full names
let names = frames.flatMap((frame) => frame.fields.map((field) => getFieldDisplayName(field, frame, frames))); let names = frames.flatMap((frame) => frame.fields.map((field) => getFieldDisplayName(field, frame, frames)));
@ -77,7 +79,9 @@ export const numberOrVariableValidator = (value: string | number) => {
if (!Number.isNaN(Number(value))) { if (!Number.isNaN(Number(value))) {
return true; return true;
} }
if (/^\$[A-Za-z0-9_]+$/.test(value)) { const variableFound = variableRegex.test(value);
variableRegex.lastIndex = 0;
if (variableFound) {
return true; return true;
} }
return false; return false;