mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 08:05:43 -06:00
FieldMatchers: Add match by value (reducer) (#64477)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
75f89e67af
commit
18e3e0ca8d
@ -1162,6 +1162,9 @@ exports[`better eslint`] = {
|
|||||||
"packages/grafana-ui/src/components/Logs/logParser.ts:5381": [
|
"packages/grafana-ui/src/components/Logs/logParser.ts:5381": [
|
||||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||||
],
|
],
|
||||||
|
"packages/grafana-ui/src/components/MatchersUI/FieldValueMatcher.tsx:5381": [
|
||||||
|
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||||
|
],
|
||||||
"packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts:5381": [
|
"packages/grafana-ui/src/components/MatchersUI/fieldMatchersUI.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
],
|
],
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { Registry } from '../utils/Registry';
|
import { Registry } from '../utils/Registry';
|
||||||
|
|
||||||
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher';
|
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher';
|
||||||
|
import { fieldValueMatcherInfo } from './matchers/fieldValueMatcher';
|
||||||
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher';
|
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher';
|
||||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates';
|
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates';
|
||||||
import { getRefIdMatchers } from './matchers/refIdMatcher';
|
import { getRefIdMatchers } from './matchers/refIdMatcher';
|
||||||
@ -21,6 +22,8 @@ import { getNumericValueMatchers } from './matchers/valueMatchers/numericMatcher
|
|||||||
import { getRangeValueMatchers } from './matchers/valueMatchers/rangeMatchers';
|
import { getRangeValueMatchers } from './matchers/valueMatchers/rangeMatchers';
|
||||||
import { getRegexValueMatcher } from './matchers/valueMatchers/regexMatchers';
|
import { getRegexValueMatcher } from './matchers/valueMatchers/regexMatchers';
|
||||||
|
|
||||||
|
export { type FieldValueMatcherConfig } from './matchers/fieldValueMatcher';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry that contains all of the built in field matchers.
|
* Registry that contains all of the built in field matchers.
|
||||||
* @public
|
* @public
|
||||||
@ -31,6 +34,7 @@ export const fieldMatchers = new Registry<FieldMatcherInfo>(() => {
|
|||||||
...getFieldTypeMatchers(), // by type
|
...getFieldTypeMatchers(), // by type
|
||||||
...getFieldNameMatchers(), // by name
|
...getFieldNameMatchers(), // by name
|
||||||
...getSimpleFieldMatchers(), // first
|
...getSimpleFieldMatchers(), // first
|
||||||
|
fieldValueMatcherInfo, // reduce field (all null/zero)
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { compareValues } from './compareValues';
|
||||||
|
|
||||||
|
describe('compare values', () => {
|
||||||
|
it('simple comparisons', () => {
|
||||||
|
expect(compareValues(null, ComparisonOperation.EQ, null)).toEqual(true);
|
||||||
|
expect(compareValues(null, ComparisonOperation.NEQ, null)).toEqual(false);
|
||||||
|
|
||||||
|
expect(compareValues(1, ComparisonOperation.GT, 2)).toEqual(false);
|
||||||
|
expect(compareValues(2, ComparisonOperation.GT, 1)).toEqual(true);
|
||||||
|
expect(compareValues(1, ComparisonOperation.GTE, 2)).toEqual(false);
|
||||||
|
expect(compareValues(2, ComparisonOperation.GTE, 1)).toEqual(true);
|
||||||
|
|
||||||
|
expect(compareValues(1, ComparisonOperation.LT, 2)).toEqual(true);
|
||||||
|
expect(compareValues(2, ComparisonOperation.LT, 1)).toEqual(false);
|
||||||
|
expect(compareValues(1, ComparisonOperation.LTE, 2)).toEqual(true);
|
||||||
|
expect(compareValues(2, ComparisonOperation.LTE, 1)).toEqual(false);
|
||||||
|
|
||||||
|
expect(compareValues(1, ComparisonOperation.EQ, 1)).toEqual(true);
|
||||||
|
expect(compareValues(1, ComparisonOperation.LTE, 1)).toEqual(true);
|
||||||
|
expect(compareValues(1, ComparisonOperation.GTE, 1)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two values
|
||||||
|
*
|
||||||
|
* @internal -- not yet exported in `@grafana/data`
|
||||||
|
*/
|
||||||
|
export function compareValues(
|
||||||
|
left: string | number | boolean | null | undefined,
|
||||||
|
op: ComparisonOperation,
|
||||||
|
right: string | number | boolean | null | undefined
|
||||||
|
) {
|
||||||
|
// Normalize null|undefined values
|
||||||
|
if (left == null || right == null) {
|
||||||
|
if (left == null) {
|
||||||
|
left = 'null';
|
||||||
|
}
|
||||||
|
if (right == null) {
|
||||||
|
right = 'null';
|
||||||
|
}
|
||||||
|
if (op === ComparisonOperation.GTE || op === ComparisonOperation.LTE) {
|
||||||
|
op = ComparisonOperation.EQ; // check for equality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case ComparisonOperation.EQ:
|
||||||
|
return `${left}` === `${right}`;
|
||||||
|
case ComparisonOperation.NEQ:
|
||||||
|
return `${left}` !== `${right}`;
|
||||||
|
case ComparisonOperation.GT:
|
||||||
|
return left > right;
|
||||||
|
case ComparisonOperation.GTE:
|
||||||
|
return left >= right;
|
||||||
|
case ComparisonOperation.LT:
|
||||||
|
return left < right;
|
||||||
|
case ComparisonOperation.LTE:
|
||||||
|
return left <= right;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { toDataFrame } from '../../dataframe/processDataFrame';
|
||||||
|
import { FieldMatcher } from '../../types';
|
||||||
|
import { DataFrame, FieldType } from '../../types/dataFrame';
|
||||||
|
import { ReducerID } from '../fieldReducer';
|
||||||
|
|
||||||
|
import { fieldValueMatcherInfo } from './fieldValueMatcher';
|
||||||
|
|
||||||
|
function getMatchingFieldNames(matcher: FieldMatcher, frame: DataFrame): string[] {
|
||||||
|
return frame.fields.filter((f) => matcher(f, frame, [])).map((f) => f.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Field Value Matcher', () => {
|
||||||
|
const testFrame = toDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: '01', type: FieldType.number, values: [0, 1] },
|
||||||
|
{ name: '02', type: FieldType.number, values: [0, 2] },
|
||||||
|
{ name: '03', type: FieldType.number, values: [0, 3] },
|
||||||
|
{ name: 'null', type: FieldType.number, values: [null, null] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
it('match nulls', () => {
|
||||||
|
expect(
|
||||||
|
getMatchingFieldNames(
|
||||||
|
fieldValueMatcherInfo.get({
|
||||||
|
reducer: ReducerID.allIsNull,
|
||||||
|
}),
|
||||||
|
testFrame
|
||||||
|
)
|
||||||
|
).toEqual(['null']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('match equals', () => {
|
||||||
|
expect(
|
||||||
|
getMatchingFieldNames(
|
||||||
|
fieldValueMatcherInfo.get({
|
||||||
|
reducer: ReducerID.lastNotNull,
|
||||||
|
op: ComparisonOperation.EQ,
|
||||||
|
value: 1,
|
||||||
|
}),
|
||||||
|
testFrame
|
||||||
|
)
|
||||||
|
).toEqual(['01']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('match equals', () => {
|
||||||
|
expect(
|
||||||
|
getMatchingFieldNames(
|
||||||
|
fieldValueMatcherInfo.get({
|
||||||
|
reducer: ReducerID.lastNotNull,
|
||||||
|
op: ComparisonOperation.GTE,
|
||||||
|
value: 2,
|
||||||
|
}),
|
||||||
|
testFrame
|
||||||
|
)
|
||||||
|
).toEqual(['02', '03']);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,58 @@
|
|||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { Field, DataFrame } from '../../types/dataFrame';
|
||||||
|
import { FieldMatcherInfo } from '../../types/transformations';
|
||||||
|
import { reduceField, ReducerID } from '../fieldReducer';
|
||||||
|
|
||||||
|
import { compareValues } from './compareValues';
|
||||||
|
import { FieldMatcherID } from './ids';
|
||||||
|
|
||||||
|
export interface FieldValueMatcherConfig {
|
||||||
|
reducer: ReducerID;
|
||||||
|
op?: ComparisonOperation;
|
||||||
|
value?: number; // or string?
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should move to a utility function on the reducer registry
|
||||||
|
function isBooleanReducer(r: ReducerID) {
|
||||||
|
return r === ReducerID.allIsNull || r === ReducerID.allIsZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fieldValueMatcherInfo: FieldMatcherInfo<FieldValueMatcherConfig> = {
|
||||||
|
id: FieldMatcherID.byValue,
|
||||||
|
name: 'By value (reducer)',
|
||||||
|
description: 'Reduce a field to a single value and test for inclusion',
|
||||||
|
|
||||||
|
// This is added to overrides by default
|
||||||
|
defaultOptions: {
|
||||||
|
reducer: ReducerID.allIsZero,
|
||||||
|
op: ComparisonOperation.GTE,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
get: (props) => {
|
||||||
|
if (!props || !props.reducer) {
|
||||||
|
return () => false;
|
||||||
|
}
|
||||||
|
let { reducer, op, value } = props;
|
||||||
|
const isBoolean = isBooleanReducer(reducer);
|
||||||
|
if (!op) {
|
||||||
|
op = ComparisonOperation.EQ;
|
||||||
|
}
|
||||||
|
return (field: Field, frame: DataFrame, allFrames: DataFrame[]) => {
|
||||||
|
const left = reduceField({
|
||||||
|
field,
|
||||||
|
reducers: [reducer],
|
||||||
|
})[reducer];
|
||||||
|
|
||||||
|
if (isBoolean) {
|
||||||
|
return Boolean(left); // boolean
|
||||||
|
}
|
||||||
|
return compareValues(left, op!, value);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getOptionsDisplayText: (props) => {
|
||||||
|
return `By value (${props.reducer})`;
|
||||||
|
},
|
||||||
|
};
|
@ -24,6 +24,7 @@ export enum FieldMatcherID {
|
|||||||
byRegexp = 'byRegexp',
|
byRegexp = 'byRegexp',
|
||||||
byRegexpOrNames = 'byRegexpOrNames',
|
byRegexpOrNames = 'byRegexpOrNames',
|
||||||
byFrameRefID = 'byFrameRefID',
|
byFrameRefID = 'byFrameRefID',
|
||||||
|
byValue = 'byValue',
|
||||||
// byIndex = 'byIndex',
|
// byIndex = 'byIndex',
|
||||||
// byLabel = 'byLabel',
|
// byLabel = 'byLabel',
|
||||||
}
|
}
|
||||||
|
@ -724,6 +724,15 @@ export interface TableColoredBackgroundCellOptions {
|
|||||||
type: TableCellDisplayMode.ColorBackground;
|
type: TableCellDisplayMode.ColorBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of a table cell
|
||||||
|
*/
|
||||||
|
export enum TableCellHeight {
|
||||||
|
Lg = 'lg',
|
||||||
|
Md = 'md',
|
||||||
|
Sm = 'sm',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table cell options. Each cell has a display mode
|
* Table cell options. Each cell has a display mode
|
||||||
* and other potential options for that display.
|
* and other potential options for that display.
|
||||||
@ -790,12 +799,15 @@ export enum LogsDedupStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Height of a table cell
|
* Compare two values
|
||||||
*/
|
*/
|
||||||
export enum TableCellHeight {
|
export enum ComparisonOperation {
|
||||||
Lg = 'lg',
|
EQ = 'eq',
|
||||||
Md = 'md',
|
GT = 'gt',
|
||||||
Sm = 'sm',
|
GTE = 'gte',
|
||||||
|
LT = 'lt',
|
||||||
|
LTE = 'lte',
|
||||||
|
NEQ = 'neq',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -255,5 +255,5 @@ Labels: {
|
|||||||
[string]: string
|
[string]: string
|
||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
|
|
||||||
// Height of a table cell
|
// Compare two values
|
||||||
TableCellHeight: "sm" | "md" | "lg" @cuetsy(kind="enum")
|
ComparisonOperation: "eq" | "neq" | "lt" | "lte" | "gt" | "gte" @cuetsy(kind="enum",memberNames="EQ|NEQ|LT|LTE|GT|GTE")
|
||||||
|
@ -67,6 +67,9 @@ TableColoredBackgroundCellOptions: {
|
|||||||
mode?: TableCellBackgroundDisplayMode
|
mode?: TableCellBackgroundDisplayMode
|
||||||
} @cuetsy(kind="interface")
|
} @cuetsy(kind="interface")
|
||||||
|
|
||||||
|
// Height of a table cell
|
||||||
|
TableCellHeight: "sm" | "md" | "lg" @cuetsy(kind="enum")
|
||||||
|
|
||||||
// Table cell options. Each cell has a display mode
|
// Table cell options. Each cell has a display mode
|
||||||
// and other potential options for that display.
|
// and other potential options for that display.
|
||||||
TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TableJsonViewCellOptions @cuetsy(kind="type")
|
TableCellOptions: TableAutoCellOptions | TableSparklineCellOptions | TableBarGaugeCellOptions | TableColoredBackgroundCellOptions | TableColorTextCellOptions | TableImageCellOptions | TableJsonViewCellOptions @cuetsy(kind="type")
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import React, { useMemo, useCallback } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FieldMatcherID,
|
||||||
|
fieldMatchers,
|
||||||
|
FieldValueMatcherConfig,
|
||||||
|
fieldReducers,
|
||||||
|
ReducerID,
|
||||||
|
SelectableValue,
|
||||||
|
GrafanaTheme2,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
|
import { Input } from '../Input/Input';
|
||||||
|
import { Select } from '../Select/Select';
|
||||||
|
|
||||||
|
import { MatcherUIProps, FieldMatcherUIRegistryItem } from './types';
|
||||||
|
|
||||||
|
type Props = MatcherUIProps<FieldValueMatcherConfig>;
|
||||||
|
|
||||||
|
export const comparisonOperationOptions = [
|
||||||
|
{ label: '==', value: ComparisonOperation.EQ },
|
||||||
|
{ label: '!=', value: ComparisonOperation.NEQ },
|
||||||
|
{ label: '>', value: ComparisonOperation.GT },
|
||||||
|
{ label: '>=', value: ComparisonOperation.GTE },
|
||||||
|
{ label: '<', value: ComparisonOperation.LT },
|
||||||
|
{ label: '<=', value: ComparisonOperation.LTE },
|
||||||
|
];
|
||||||
|
|
||||||
|
// This should move to a utility function on the reducer registry
|
||||||
|
function isBooleanReducer(r: ReducerID) {
|
||||||
|
return r === ReducerID.allIsNull || r === ReducerID.allIsZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FieldValueMatcherEditor = ({ options, onChange }: Props) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const reducer = useMemo(() => fieldReducers.selectOptions([options?.reducer]), [options?.reducer]);
|
||||||
|
|
||||||
|
const onSetReducer = useCallback(
|
||||||
|
(selection: SelectableValue<string>) => {
|
||||||
|
return onChange({ ...options, reducer: selection.value! as ReducerID });
|
||||||
|
},
|
||||||
|
[options, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeOp = useCallback(
|
||||||
|
(v: SelectableValue<ComparisonOperation>) => {
|
||||||
|
return onChange({ ...options, op: v.value! });
|
||||||
|
},
|
||||||
|
[options, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeValue = useCallback(
|
||||||
|
(e: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.currentTarget.valueAsNumber;
|
||||||
|
return onChange({ ...options, value });
|
||||||
|
},
|
||||||
|
[options, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const opts = options ?? {};
|
||||||
|
const isBool = isBooleanReducer(options.reducer);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.spot}>
|
||||||
|
<Select
|
||||||
|
value={reducer.current}
|
||||||
|
options={reducer.options}
|
||||||
|
onChange={onSetReducer}
|
||||||
|
placeholder="Select field reducer"
|
||||||
|
/>
|
||||||
|
{opts.reducer && !isBool && (
|
||||||
|
<>
|
||||||
|
<Select
|
||||||
|
value={comparisonOperationOptions.find((v) => v.value === opts.op)}
|
||||||
|
options={comparisonOperationOptions}
|
||||||
|
onChange={onChangeOp}
|
||||||
|
aria-label={'Comparison operator'}
|
||||||
|
width={19}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input type="number" value={opts.value} onChange={onChangeValue} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
spot: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
align-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fieldValueMatcherItem: FieldMatcherUIRegistryItem<FieldValueMatcherConfig> = {
|
||||||
|
id: FieldMatcherID.byValue,
|
||||||
|
component: FieldValueMatcherEditor,
|
||||||
|
matcher: fieldMatchers.get(FieldMatcherID.byValue),
|
||||||
|
name: 'Fields with values',
|
||||||
|
description: 'Set properties for fields with reducer condition',
|
||||||
|
optionsToLabel: (options) => `${options?.reducer} ${options?.op} ${options?.value}`,
|
||||||
|
};
|
@ -4,6 +4,7 @@ import { fieldNameByRegexMatcherItem } from './FieldNameByRegexMatcherEditor';
|
|||||||
import { fieldNameMatcherItem } from './FieldNameMatcherEditor';
|
import { fieldNameMatcherItem } from './FieldNameMatcherEditor';
|
||||||
import { fieldNamesMatcherItem } from './FieldNamesMatcherEditor';
|
import { fieldNamesMatcherItem } from './FieldNamesMatcherEditor';
|
||||||
import { fieldTypeMatcherItem } from './FieldTypeMatcherEditor';
|
import { fieldTypeMatcherItem } from './FieldTypeMatcherEditor';
|
||||||
|
import { fieldValueMatcherItem } from './FieldValueMatcher';
|
||||||
import { fieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher';
|
import { fieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher';
|
||||||
import { FieldMatcherUIRegistryItem } from './types';
|
import { FieldMatcherUIRegistryItem } from './types';
|
||||||
|
|
||||||
@ -13,4 +14,5 @@ export const fieldMatchersUI = new Registry<FieldMatcherUIRegistryItem<any>>(()
|
|||||||
fieldTypeMatcherItem,
|
fieldTypeMatcherItem,
|
||||||
fieldsByFrameRefIdItem,
|
fieldsByFrameRefIdItem,
|
||||||
fieldNamesMatcherItem,
|
fieldNamesMatcherItem,
|
||||||
|
fieldValueMatcherItem,
|
||||||
]);
|
]);
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
DynamicConfigValue,
|
DynamicConfigValue,
|
||||||
ConfigOverrideRule,
|
ConfigOverrideRule,
|
||||||
GrafanaTheme2,
|
GrafanaTheme2,
|
||||||
|
fieldMatchers,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { fieldMatchersUI, useStyles2, ValuePicker } from '@grafana/ui';
|
import { fieldMatchersUI, useStyles2, ValuePicker } from '@grafana/ui';
|
||||||
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||||
@ -46,13 +47,19 @@ export function getFieldOverrideCategories(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onOverrideAdd = (value: SelectableValue<string>) => {
|
const onOverrideAdd = (value: SelectableValue<string>) => {
|
||||||
|
const info = fieldMatchers.get(value.value!);
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
props.onFieldConfigsChange({
|
props.onFieldConfigsChange({
|
||||||
...currentFieldConfig,
|
...currentFieldConfig,
|
||||||
overrides: [
|
overrides: [
|
||||||
...currentFieldConfig.overrides,
|
...currentFieldConfig.overrides,
|
||||||
{
|
{
|
||||||
matcher: {
|
matcher: {
|
||||||
id: value.value!,
|
id: info.id,
|
||||||
|
options: info.defaultOptions,
|
||||||
},
|
},
|
||||||
properties: [],
|
properties: [],
|
||||||
},
|
},
|
||||||
|
@ -5,12 +5,14 @@ import { useObservable } from 'react-use';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue, StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue, StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
|
||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
import { Button, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui';
|
import { Button, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui';
|
||||||
|
import { comparisonOperationOptions } from '@grafana/ui/src/components/MatchersUI/FieldValueMatcher';
|
||||||
import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
|
import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
|
||||||
|
|
||||||
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
|
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
|
||||||
import { defaultStyleConfig, StyleConfig } from '../style/types';
|
import { defaultStyleConfig, StyleConfig } from '../style/types';
|
||||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
|
import { FeatureStyleConfig } from '../types';
|
||||||
import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures';
|
import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures';
|
||||||
import { getSelectionInfo } from '../utils/selection';
|
import { getSelectionInfo } from '../utils/selection';
|
||||||
|
|
||||||
@ -21,15 +23,6 @@ export interface StyleRuleEditorSettings {
|
|||||||
layerInfo: Observable<LayerContentInfo>;
|
layerInfo: Observable<LayerContentInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const comparators = [
|
|
||||||
{ label: '==', value: ComparisonOperation.EQ },
|
|
||||||
{ label: '!=', value: ComparisonOperation.NEQ },
|
|
||||||
{ label: '>', value: ComparisonOperation.GT },
|
|
||||||
{ label: '>=', value: ComparisonOperation.GTE },
|
|
||||||
{ label: '<', value: ComparisonOperation.LT },
|
|
||||||
{ label: '<=', value: ComparisonOperation.LTE },
|
|
||||||
];
|
|
||||||
|
|
||||||
type Props = StandardEditorProps<FeatureStyleConfig, any, unknown, StyleRuleEditorSettings>;
|
type Props = StandardEditorProps<FeatureStyleConfig, any, unknown, StyleRuleEditorSettings>;
|
||||||
|
|
||||||
export const StyleRuleEditor = ({ value, onChange, item, context }: Props) => {
|
export const StyleRuleEditor = ({ value, onChange, item, context }: Props) => {
|
||||||
@ -148,8 +141,8 @@ export const StyleRuleEditor = ({ value, onChange, item, context }: Props) => {
|
|||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField className={styles.inline}>
|
<InlineField className={styles.inline}>
|
||||||
<Select
|
<Select
|
||||||
value={comparators.find((v) => v.value === check.operation)}
|
value={comparisonOperationOptions.find((v) => v.value === check.operation)}
|
||||||
options={comparators}
|
options={comparisonOperationOptions}
|
||||||
onChange={onChangeComparison}
|
onChange={onChangeComparison}
|
||||||
aria-label={'Comparison operator'}
|
aria-label={'Comparison operator'}
|
||||||
width={8}
|
width={8}
|
||||||
|
@ -12,7 +12,7 @@ import VectorSource from 'ol/source/Vector';
|
|||||||
import GeoJSON from 'ol/format/GeoJSON';
|
import GeoJSON from 'ol/format/GeoJSON';
|
||||||
import { unByKey } from 'ol/Observable';
|
import { unByKey } from 'ol/Observable';
|
||||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||||
import { ComparisonOperation, FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
import { FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||||
import { Fill, Stroke, Style } from 'ol/style';
|
import { Fill, Stroke, Style } from 'ol/style';
|
||||||
import { FeatureLike } from 'ol/Feature';
|
import { FeatureLike } from 'ol/Feature';
|
||||||
import { defaultStyleConfig, StyleConfig, StyleConfigState } from '../../style/types';
|
import { defaultStyleConfig, StyleConfig, StyleConfigState } from '../../style/types';
|
||||||
@ -24,6 +24,7 @@ import { map as rxjsmap, first } from 'rxjs/operators';
|
|||||||
import { getLayerPropertyInfo } from '../../utils/getFeatures';
|
import { getLayerPropertyInfo } from '../../utils/getFeatures';
|
||||||
import { findField } from 'app/features/dimensions';
|
import { findField } from 'app/features/dimensions';
|
||||||
import { getStyleDimension, getPublicGeoJSONFiles } from '../../utils/utils';
|
import { getStyleDimension, getPublicGeoJSONFiles } from '../../utils/utils';
|
||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
export interface DynamicGeoJSONMapperConfig {
|
export interface DynamicGeoJSONMapperConfig {
|
||||||
// URL for a geojson file
|
// URL for a geojson file
|
||||||
|
@ -11,7 +11,7 @@ import VectorSource from 'ol/source/Vector';
|
|||||||
import GeoJSON from 'ol/format/GeoJSON';
|
import GeoJSON from 'ol/format/GeoJSON';
|
||||||
import { unByKey } from 'ol/Observable';
|
import { unByKey } from 'ol/Observable';
|
||||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||||
import { ComparisonOperation, FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
import { FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||||
import { Style } from 'ol/style';
|
import { Style } from 'ol/style';
|
||||||
import { FeatureLike } from 'ol/Feature';
|
import { FeatureLike } from 'ol/Feature';
|
||||||
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
|
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
|
||||||
@ -23,6 +23,7 @@ import { ReplaySubject } from 'rxjs';
|
|||||||
import { map as rxjsmap, first } from 'rxjs/operators';
|
import { map as rxjsmap, first } from 'rxjs/operators';
|
||||||
import { getLayerPropertyInfo } from '../../utils/getFeatures';
|
import { getLayerPropertyInfo } from '../../utils/getFeatures';
|
||||||
import { getPublicGeoJSONFiles } from '../../utils/utils';
|
import { getPublicGeoJSONFiles } from '../../utils/utils';
|
||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
export interface GeoJSONMapperConfig {
|
export interface GeoJSONMapperConfig {
|
||||||
// URL for a geojson file
|
// URL for a geojson file
|
||||||
|
@ -5,6 +5,7 @@ import BaseLayer from 'ol/layer/Base';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
import { MapLayerHandler, MapLayerOptions } from '@grafana/data';
|
import { MapLayerHandler, MapLayerOptions } from '@grafana/data';
|
||||||
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
import { LayerElement } from 'app/core/components/Layers/types';
|
import { LayerElement } from 'app/core/components/Layers/types';
|
||||||
|
|
||||||
import { ControlsOptions as ControlsOptionsBase } from './panelcfg.gen';
|
import { ControlsOptions as ControlsOptionsBase } from './panelcfg.gen';
|
||||||
@ -40,15 +41,6 @@ export interface GeomapInstanceState {
|
|||||||
actions: GeomapLayerActions;
|
actions: GeomapLayerActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ComparisonOperation {
|
|
||||||
EQ = 'eq',
|
|
||||||
NEQ = 'neq',
|
|
||||||
LT = 'lt',
|
|
||||||
LTE = 'lte',
|
|
||||||
GT = 'gt',
|
|
||||||
GTE = 'gte',
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------
|
//-------------------
|
||||||
// Runtime model
|
// Runtime model
|
||||||
//-------------------
|
//-------------------
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Feature } from 'ol';
|
import { Feature } from 'ol';
|
||||||
|
|
||||||
import { ComparisonOperation } from '../types';
|
import { ComparisonOperation } from '@grafana/schema';
|
||||||
|
|
||||||
import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule';
|
import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule';
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { FeatureLike } from 'ol/Feature';
|
import { FeatureLike } from 'ol/Feature';
|
||||||
|
|
||||||
import { FeatureRuleConfig, ComparisonOperation } from '../types';
|
import { compareValues } from '@grafana/data/src/transformations/matchers/compareValues';
|
||||||
|
|
||||||
|
import { FeatureRuleConfig } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether feature has property value that matches rule
|
* Check whether feature has property value that matches rule
|
||||||
@ -10,20 +12,5 @@ import { FeatureRuleConfig, ComparisonOperation } from '../types';
|
|||||||
*/
|
*/
|
||||||
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
|
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
|
||||||
const val = feature.get(rule.property);
|
const val = feature.get(rule.property);
|
||||||
switch (rule.operation) {
|
return compareValues(val, rule.operation, rule.value);
|
||||||
case ComparisonOperation.EQ:
|
|
||||||
return `${val}` === `${rule.value}`;
|
|
||||||
case ComparisonOperation.NEQ:
|
|
||||||
return val !== rule.value;
|
|
||||||
case ComparisonOperation.GT:
|
|
||||||
return val > rule.value;
|
|
||||||
case ComparisonOperation.GTE:
|
|
||||||
return val >= rule.value;
|
|
||||||
case ComparisonOperation.LT:
|
|
||||||
return val < rule.value;
|
|
||||||
case ComparisonOperation.LTE:
|
|
||||||
return val <= rule.value;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user