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": [
|
||||
[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": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
import { Registry } from '../utils/Registry';
|
||||
|
||||
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher';
|
||||
import { fieldValueMatcherInfo } from './matchers/fieldValueMatcher';
|
||||
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher';
|
||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates';
|
||||
import { getRefIdMatchers } from './matchers/refIdMatcher';
|
||||
@ -21,6 +22,8 @@ import { getNumericValueMatchers } from './matchers/valueMatchers/numericMatcher
|
||||
import { getRangeValueMatchers } from './matchers/valueMatchers/rangeMatchers';
|
||||
import { getRegexValueMatcher } from './matchers/valueMatchers/regexMatchers';
|
||||
|
||||
export { type FieldValueMatcherConfig } from './matchers/fieldValueMatcher';
|
||||
|
||||
/**
|
||||
* Registry that contains all of the built in field matchers.
|
||||
* @public
|
||||
@ -31,6 +34,7 @@ export const fieldMatchers = new Registry<FieldMatcherInfo>(() => {
|
||||
...getFieldTypeMatchers(), // by type
|
||||
...getFieldNameMatchers(), // by name
|
||||
...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',
|
||||
byRegexpOrNames = 'byRegexpOrNames',
|
||||
byFrameRefID = 'byFrameRefID',
|
||||
byValue = 'byValue',
|
||||
// byIndex = 'byIndex',
|
||||
// byLabel = 'byLabel',
|
||||
}
|
||||
|
@ -724,6 +724,15 @@ export interface TableColoredBackgroundCellOptions {
|
||||
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
|
||||
* 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 {
|
||||
Lg = 'lg',
|
||||
Md = 'md',
|
||||
Sm = 'sm',
|
||||
export enum ComparisonOperation {
|
||||
EQ = 'eq',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
NEQ = 'neq',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,5 +255,5 @@ Labels: {
|
||||
[string]: string
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// Height of a table cell
|
||||
TableCellHeight: "sm" | "md" | "lg" @cuetsy(kind="enum")
|
||||
// Compare two values
|
||||
ComparisonOperation: "eq" | "neq" | "lt" | "lte" | "gt" | "gte" @cuetsy(kind="enum",memberNames="EQ|NEQ|LT|LTE|GT|GTE")
|
||||
|
@ -67,6 +67,9 @@ TableColoredBackgroundCellOptions: {
|
||||
mode?: TableCellBackgroundDisplayMode
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// Height of a table cell
|
||||
TableCellHeight: "sm" | "md" | "lg" @cuetsy(kind="enum")
|
||||
|
||||
// Table cell options. Each cell has a display mode
|
||||
// and other potential options for that display.
|
||||
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 { fieldNamesMatcherItem } from './FieldNamesMatcherEditor';
|
||||
import { fieldTypeMatcherItem } from './FieldTypeMatcherEditor';
|
||||
import { fieldValueMatcherItem } from './FieldValueMatcher';
|
||||
import { fieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher';
|
||||
import { FieldMatcherUIRegistryItem } from './types';
|
||||
|
||||
@ -13,4 +14,5 @@ export const fieldMatchersUI = new Registry<FieldMatcherUIRegistryItem<any>>(()
|
||||
fieldTypeMatcherItem,
|
||||
fieldsByFrameRefIdItem,
|
||||
fieldNamesMatcherItem,
|
||||
fieldValueMatcherItem,
|
||||
]);
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
DynamicConfigValue,
|
||||
ConfigOverrideRule,
|
||||
GrafanaTheme2,
|
||||
fieldMatchers,
|
||||
} from '@grafana/data';
|
||||
import { fieldMatchersUI, useStyles2, ValuePicker } from '@grafana/ui';
|
||||
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||
@ -46,13 +47,19 @@ export function getFieldOverrideCategories(
|
||||
};
|
||||
|
||||
const onOverrideAdd = (value: SelectableValue<string>) => {
|
||||
const info = fieldMatchers.get(value.value!);
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onFieldConfigsChange({
|
||||
...currentFieldConfig,
|
||||
overrides: [
|
||||
...currentFieldConfig.overrides,
|
||||
{
|
||||
matcher: {
|
||||
id: value.value!,
|
||||
id: info.id,
|
||||
options: info.defaultOptions,
|
||||
},
|
||||
properties: [],
|
||||
},
|
||||
|
@ -5,12 +5,14 @@ import { useObservable } from 'react-use';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue, StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
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 { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
|
||||
import { defaultStyleConfig, StyleConfig } from '../style/types';
|
||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
|
||||
import { FeatureStyleConfig } from '../types';
|
||||
import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures';
|
||||
import { getSelectionInfo } from '../utils/selection';
|
||||
|
||||
@ -21,15 +23,6 @@ export interface StyleRuleEditorSettings {
|
||||
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>;
|
||||
|
||||
export const StyleRuleEditor = ({ value, onChange, item, context }: Props) => {
|
||||
@ -148,8 +141,8 @@ export const StyleRuleEditor = ({ value, onChange, item, context }: Props) => {
|
||||
</InlineField>
|
||||
<InlineField className={styles.inline}>
|
||||
<Select
|
||||
value={comparators.find((v) => v.value === check.operation)}
|
||||
options={comparators}
|
||||
value={comparisonOperationOptions.find((v) => v.value === check.operation)}
|
||||
options={comparisonOperationOptions}
|
||||
onChange={onChangeComparison}
|
||||
aria-label={'Comparison operator'}
|
||||
width={8}
|
||||
|
@ -12,7 +12,7 @@ import VectorSource from 'ol/source/Vector';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { unByKey } from 'ol/Observable';
|
||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||
import { ComparisonOperation, FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||
import { FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||
import { Fill, Stroke, Style } from 'ol/style';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
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 { findField } from 'app/features/dimensions';
|
||||
import { getStyleDimension, getPublicGeoJSONFiles } from '../../utils/utils';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
|
||||
export interface DynamicGeoJSONMapperConfig {
|
||||
// URL for a geojson file
|
||||
|
@ -11,7 +11,7 @@ import VectorSource from 'ol/source/Vector';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { unByKey } from 'ol/Observable';
|
||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||
import { ComparisonOperation, FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||
import { FeatureRuleConfig, FeatureStyleConfig } from '../../types';
|
||||
import { Style } from 'ol/style';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
|
||||
@ -23,6 +23,7 @@ import { ReplaySubject } from 'rxjs';
|
||||
import { map as rxjsmap, first } from 'rxjs/operators';
|
||||
import { getLayerPropertyInfo } from '../../utils/getFeatures';
|
||||
import { getPublicGeoJSONFiles } from '../../utils/utils';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
|
||||
export interface GeoJSONMapperConfig {
|
||||
// URL for a geojson file
|
||||
|
@ -5,6 +5,7 @@ import BaseLayer from 'ol/layer/Base';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { MapLayerHandler, MapLayerOptions } from '@grafana/data';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
import { LayerElement } from 'app/core/components/Layers/types';
|
||||
|
||||
import { ControlsOptions as ControlsOptionsBase } from './panelcfg.gen';
|
||||
@ -40,15 +41,6 @@ export interface GeomapInstanceState {
|
||||
actions: GeomapLayerActions;
|
||||
}
|
||||
|
||||
export enum ComparisonOperation {
|
||||
EQ = 'eq',
|
||||
NEQ = 'neq',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
}
|
||||
|
||||
//-------------------
|
||||
// Runtime model
|
||||
//-------------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Feature } from 'ol';
|
||||
|
||||
import { ComparisonOperation } from '../types';
|
||||
import { ComparisonOperation } from '@grafana/schema';
|
||||
|
||||
import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule';
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
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
|
||||
@ -10,20 +12,5 @@ import { FeatureRuleConfig, ComparisonOperation } from '../types';
|
||||
*/
|
||||
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
|
||||
const val = feature.get(rule.property);
|
||||
switch (rule.operation) {
|
||||
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;
|
||||
}
|
||||
return compareValues(val, rule.operation, rule.value);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user