From 7dec8cb755a304b29b8f61ffaa34ca502c3f4a2c Mon Sep 17 00:00:00 2001 From: Andy McHugh Date: Mon, 6 May 2024 16:22:49 +0100 Subject: [PATCH] add valueMapping support. See changelog for details. --- CHANGELOG.md | 21 +-- provisioning/dashboardData/dataMappings.svg | 4 + provisioning/dashboardData/dataMappings.yaml | 56 ++++++++ .../dashboardData/dataMappingsSite.yaml | 14 ++ .../dashboardData/dataMappings_sparse.yaml | 57 ++++++++ provisioning/dashboards/valueMappings.json | 130 ++++++++++++++++++ src/components/Config.tsx | 23 ++++ src/components/SvgUpdater.tsx | 25 +++- src/test/SvgUpdater.test.tsx | 32 ++++- yaml_defs/panelConfig.yaml | 22 +++ yaml_defs/siteConfig.yaml | 15 +- 11 files changed, 386 insertions(+), 13 deletions(-) create mode 100644 provisioning/dashboardData/dataMappings.svg create mode 100644 provisioning/dashboardData/dataMappings.yaml create mode 100644 provisioning/dashboardData/dataMappingsSite.yaml create mode 100644 provisioning/dashboardData/dataMappings_sparse.yaml create mode 100644 provisioning/dashboards/valueMappings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ce18665..534d37f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,7 @@ animated lines that are controllable via 'direction' and 'duration' attributes. change adds yaml config to drive these animations off of data. When such config is present a play/pause button is always added to the panel to ensure the panel is usable by all. New config terms: -- cells.cell-name.flowAnimation.dataRef -- cells.cell-name.flowAnimation.datapoint -- cells.cell-name.flowAnimation.thresholdOffValue -- cells.cell-name.flowAnimation.thresholdLwrValue -- cells.cell-name.flowAnimation.thresholdLwrDurationSecs -- cells.cell-name.flowAnimation.thresholdUprValue -- cells.cell-name.flowAnimation.thresholdUprDurationSecs -- cells.cell-name.flowAnimation.unidirectional +- cells.cell-name.flowAnimation As well as provisioning dashboards, the above functionality is demonstrated in example 4: - svg: https://raw.githubusercontent.com/andymchugh/andrewbmchugh-flow-panel/main/examples/darkThemeSvg4.svg @@ -30,6 +23,18 @@ One Datapoint Interpolation fix ------------------------------- Fixes a bug with timeSeries interpolation which resulted in no data when the timeSeries only had a single datapoint. +Value Mappings Support +---------------------- +This allows you to configure a custom value based on data-match criteria. i.e. if your data is 0, 1, 2 you can use +this configuration to replace the 'label' text with 'auto', open', 'closed'. The criteria you can use to match are: +- exact value match +- range match +- partial range match. + +New panelConfig terms: +- cells.cell-name.label.valueMappings +- cells.cell-name.label.valueMappingsRef + ## 1.10.0 Increases the range of SVG shape support by relaxing the expected DOM element hierachy. diff --git a/provisioning/dashboardData/dataMappings.svg b/provisioning/dashboardData/dataMappings.svg new file mode 100644 index 0000000..2cfd2e3 --- /dev/null +++ b/provisioning/dashboardData/dataMappings.svg @@ -0,0 +1,4 @@ + + + +
nodata
nodata
Transactions
Transactions
nodata
nodata
Transactions
Transactions
Text is not SVG - cannot display
\ No newline at end of file diff --git a/provisioning/dashboardData/dataMappings.yaml b/provisioning/dashboardData/dataMappings.yaml new file mode 100644 index 0000000..17a8333 --- /dev/null +++ b/provisioning/dashboardData/dataMappings.yaml @@ -0,0 +1,56 @@ +--- + +#------------------------------------------------------------------------------ +# YAML Aliases to simplify maintenance + +test: + testDataExtendedZero: true + +anchorLinks: + - valueMappings: &valuemap1 + - {value: null, text: "panel null"} + - {value: 0, text: "panel off"} + - {valueMax: 300, text: "panel low"} + - {valueMin: 300, valueMax: 600, text: "panel medium"} + - {valueMin: 600, text: "panel high"} + - thresholds: &thresholds + - {color: "green", level: 0} + - {color: "orange", level: 500} + - {color: "red", level: 1000} + +#------------------------------------------------------------------------------ +# Panel Config + +cellIdPreamble: "cell-" +gradientMode: "hue" +cells: + dbtrans1: + dataRef: "test-data-large-cos" + label: + separator: "replace" + units: "ops" + valueMappings: *valuemap1 + labelColor: + thresholds: *thresholds + dbtrans2: + dataRef: "test-data-large-cos" + label: + separator: "cr" + units: "ops" + fillColor: + thresholds: *thresholds + dbtrans3: + dataRef: "test-data-large-cos" + label: + separator: "replace" + units: "ops" + valueMappingsRef: "dbTrans" + labelColor: + thresholds: *thresholds + dbtrans4: + dataRef: "test-data-large-cos" + label: + separator: "cr" + units: "ops" + fillColor: + thresholds: *thresholds diff --git a/provisioning/dashboardData/dataMappingsSite.yaml b/provisioning/dashboardData/dataMappingsSite.yaml new file mode 100644 index 0000000..1b40a22 --- /dev/null +++ b/provisioning/dashboardData/dataMappingsSite.yaml @@ -0,0 +1,14 @@ +--- + +colors: + red: "#DD0000" + amber: "#CC8C00" + green: "#00AA00" + +valueMappings: + dbTrans: + - {value: null, text: "site null"} + - {value: 0, text: "site off"} + - {valueMax: 300, text: "site low"} + - {valueMin: 300, valueMax: 600, text: "site medium"} + - {valueMin: 600, text: "site high"} diff --git a/provisioning/dashboardData/dataMappings_sparse.yaml b/provisioning/dashboardData/dataMappings_sparse.yaml new file mode 100644 index 0000000..5d19e29 --- /dev/null +++ b/provisioning/dashboardData/dataMappings_sparse.yaml @@ -0,0 +1,57 @@ +--- + +#------------------------------------------------------------------------------ +# YAML Aliases to simplify maintenance + +test: + testDataExtendedZero: true + testDataSparse: true + +anchorLinks: + - valueMappings: &valuemap1 + - {value: null, text: "panel null"} + - {value: 0, text: "panel off"} + - {valueMax: 300, text: "panel low"} + - {valueMin: 300, valueMax: 600, text: "panel medium"} + - {valueMin: 600, text: "panel high"} + - thresholds: &thresholds + - {color: "green", level: 0} + - {color: "orange", level: 500} + - {color: "red", level: 1000} + +#------------------------------------------------------------------------------ +# Panel Config + +cellIdPreamble: "cell-" +gradientMode: "hue" +cells: + dbtrans1: + dataRef: "test-data-large-cos" + label: + separator: "replace" + units: "ops" + valueMappings: *valuemap1 + labelColor: + thresholds: *thresholds + dbtrans2: + dataRef: "test-data-large-cos" + label: + separator: "cr" + units: "ops" + fillColor: + thresholds: *thresholds + dbtrans3: + dataRef: "test-data-large-cos" + label: + separator: "replace" + units: "ops" + valueMappingsRef: "dbTrans" + labelColor: + thresholds: *thresholds + dbtrans4: + dataRef: "test-data-large-cos" + label: + separator: "cr" + units: "ops" + fillColor: + thresholds: *thresholds diff --git a/provisioning/dashboards/valueMappings.json b/provisioning/dashboards/valueMappings.json new file mode 100644 index 0000000..32c514f --- /dev/null +++ b/provisioning/dashboards/valueMappings.json @@ -0,0 +1,130 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 5, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 8, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Value Mappings\n\nThis dashboard demonstrates value mappings.\n\nThe panel supports range based substitution of numbers and nulls at\nboth the panelConfig level and the siteConfig level.", + "mode": "markdown" + }, + "pluginVersion": "10.0.0", + "type": "text" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 5 + }, + "id": 7, + "options": { + "animationsEnabled": true, + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "highlighterEnabled": true, + "panZoomEnabled": true, + "panelConfig": "---\n\n#------------------------------------------------------------------------------\n# YAML Aliases to simplify maintenance\n\ntest:\n testDataExtendedZero: true\n\nanchorLinks:\n - valueMappings: &valuemap1\n - {value: null, text: \"panel null\"}\n - {value: 0, text: \"panel off\"}\n - {valueMax: 300, text: \"panel low\"}\n - {valueMin: 300, valueMax: 600, text: \"panel medium\"}\n - {valueMin: 600, text: \"panel high\"}\n - thresholds: &thresholds\n - {color: \"green\", level: 0}\n - {color: \"orange\", level: 500}\n - {color: \"red\", level: 1000}\n\n#------------------------------------------------------------------------------\n# Panel Config\n\ncellIdPreamble: \"cell-\"\ngradientMode: \"hue\"\ncells: \n dbtrans1:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"replace\"\n units: \"ops\"\n valueMappings: *valuemap1\n labelColor:\n thresholds: *thresholds\n dbtrans2:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"cr\"\n units: \"ops\"\n fillColor:\n thresholds: *thresholds\n dbtrans3:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"replace\"\n units: \"ops\"\n valueMappingsRef: \"dbTrans\"\n labelColor:\n thresholds: *thresholds\n dbtrans4:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"cr\"\n units: \"ops\"\n fillColor:\n thresholds: *thresholds", + "siteConfig": "---\n\ncolors:\n red: \"#DD0000\"\n amber: \"#CC8C00\"\n green: \"#00AA00\"\n\nvalueMappings:\n dbTrans:\n - {value: null, text: \"site null\"}\n - {value: 0, text: \"site off\"}\n - {valueMax: 300, text: \"site low\"}\n - {valueMin: 300, valueMax: 600, text: \"site medium\"}\n - {valueMin: 600, text: \"site high\"}", + "svg": "\n\n\n
nodata
nodata
Transactions
Transactions
nodata
nodata
Transactions
Transactions
Text is not SVG - cannot display
", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Continuous Data", + "type": "andrewbmchugh-flow-panel" + }, + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 5 + }, + "id": 9, + "options": { + "animationsEnabled": true, + "debuggingCtr": { + "colorsCtr": 0, + "dataCtr": 0, + "displaySvgCtr": 0, + "mappingsCtr": 0, + "timingsCtr": 0 + }, + "highlighterEnabled": true, + "panZoomEnabled": true, + "panelConfig": "---\n\n#------------------------------------------------------------------------------\n# YAML Aliases to simplify maintenance\n\ntest:\n testDataExtendedZero: true\n testDataSparse: true\n\nanchorLinks:\n - valueMappings: &valuemap1\n - {value: null, text: \"panel null\"}\n - {value: 0, text: \"panel off\"}\n - {valueMax: 300, text: \"panel low\"}\n - {valueMin: 300, valueMax: 600, text: \"panel medium\"}\n - {valueMin: 600, text: \"panel high\"}\n - thresholds: &thresholds\n - {color: \"green\", level: 0}\n - {color: \"orange\", level: 500}\n - {color: \"red\", level: 1000}\n\n#------------------------------------------------------------------------------\n# Panel Config\n\ncellIdPreamble: \"cell-\"\ngradientMode: \"hue\"\ncells: \n dbtrans1:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"replace\"\n units: \"ops\"\n valueMappings: *valuemap1\n labelColor:\n thresholds: *thresholds\n dbtrans2:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"cr\"\n units: \"ops\"\n fillColor:\n thresholds: *thresholds\n dbtrans3:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"replace\"\n units: \"ops\"\n valueMappingsRef: \"dbTrans\"\n labelColor:\n thresholds: *thresholds\n dbtrans4:\n dataRef: \"test-data-large-cos\"\n label:\n separator: \"cr\"\n units: \"ops\"\n fillColor:\n thresholds: *thresholds", + "siteConfig": "---\n\ncolors:\n red: \"#DD0000\"\n amber: \"#CC8C00\"\n green: \"#00AA00\"\n\nvalueMappings:\n dbTrans:\n - {value: null, text: \"site null\"}\n - {value: 0, text: \"site off\"}\n - {valueMax: 300, text: \"site low\"}\n - {valueMin: 300, valueMax: 600, text: \"site medium\"}\n - {valueMin: 600, text: \"site high\"}", + "svg": "\n\n\n
nodata
nodata
Transactions
Transactions
nodata
nodata
Transactions
Transactions
Text is not SVG - cannot display
", + "testDataEnabled": true, + "timeSliderEnabled": true + }, + "title": "Sparse Data", + "type": "andrewbmchugh-flow-panel" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Value Mappings", + "version": 1, + "weekStart": "" + } \ No newline at end of file diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 4eae0e3..2408daa 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -30,6 +30,14 @@ export type TestConfig = { testDataExtendedZero: boolean | undefined; }; +export type FlowValueMapping = { + value: number | null | undefined; + valueMin: number | undefined; + valueMax: number | undefined; + text: string; + valid: boolean; +}; + export type PanelConfigCellLabel = { dataRef: string | undefined; datapoint: DatapointMode | undefined; @@ -37,6 +45,8 @@ export type PanelConfigCellLabel = { units: string | undefined; unitsPostfix: string | undefined; decimalPoints: number | null | undefined; + valueMappingsRef: string | undefined; + valueMappings: FlowValueMapping[] | undefined; }; export type PanelConfigCellColor = { @@ -89,6 +99,7 @@ export type SiteConfig = { links: Map; colors: Map; thresholds: Map; + valueMappings: Map; }; export type PanelConfig = { @@ -152,6 +163,7 @@ export function siteConfigFactory(config: any) { colors: new Map(Object.entries(config.colors || {})), variableThresholdScalars: new Map(Object.entries(config.variableThresholdScalars || {})), thresholds: new Map(Object.entries(config.thresholds || {})), + valueMappings: new Map(Object.entries(config.valueMappings || {})), } as SiteConfig; } @@ -194,6 +206,17 @@ function panelConfigDereference(siteConfig: SiteConfig, panelConfig: PanelConfig if (typeof cell.label.datapoint === 'undefined') { cell.label.datapoint = cell.datapoint || panelConfig.datapoint; } + if (!cell.label.valueMappings && cell.label.valueMappingsRef) { + cell.label.valueMappings = siteConfig.valueMappings.get(cell.label.valueMappingsRef); + } + if (cell.label.valueMappings) { + for (let mapping of cell.label.valueMappings) { + mapping.valid = mapping.valid || ( + (typeof mapping.text === 'string') && + ((typeof mapping.valueMin === 'number') || (typeof mapping.valueMin === 'undefined')) && + ((typeof mapping.valueMax === 'number') || (typeof mapping.valueMax === 'undefined'))); + } + } } if (typeof cell.datapoint === 'undefined') { cell.datapoint = panelConfig.datapoint; diff --git a/src/components/SvgUpdater.tsx b/src/components/SvgUpdater.tsx index 630fb92..8e34816 100644 --- a/src/components/SvgUpdater.tsx +++ b/src/components/SvgUpdater.tsx @@ -1,6 +1,6 @@ import { getValueFormatterIndex, formattedValueToString, GrafanaTheme2 } from '@grafana/data'; import { - DatapointMode, HighlightFactors, + DatapointMode, FlowValueMapping, HighlightFactors, LabelSeparator, Link, PanelConfig, PanelConfigCell, PanelConfigCellColor, PanelConfigCellFlowAnimation, PanelConfigCellLabel, SiteConfig, VariableThresholdScalars } from 'components/Config'; @@ -210,6 +210,26 @@ function getCellValue(datapoint: DatapointMode | undefined, tsName: string, tsDa return value; } +export function valueMapping(valueMappings: FlowValueMapping[], value: number | null) { + for (const mapping of valueMappings) { + if (mapping.valid) { + let match = false; + if (typeof mapping.value !== 'undefined') { + match = (value === mapping.value); + } + else if (typeof value === 'number') { + match = + ((typeof mapping.valueMin === 'undefined') || (value >= mapping.valueMin)) && + ((typeof mapping.valueMax === 'undefined') || (value <= mapping.valueMax)); + } + if (match) { + return mapping.text; + } + } + } + return null; +} + function formatCellValue(cellLabelData: PanelConfigCellLabel, value: number) { const format = cellLabelData.units || 'none'; const decimalPoints = cellLabelData.decimalPoints; @@ -291,7 +311,8 @@ export function svgUpdate(svgHolder: SvgHolder, tsData: TimeSeriesData, highligh const cellLabelData = cellData.cellProps.label; const cellLabelDatapoint = cellLabelData?.datapoint; const cellLabelValue = cellLabelData?.dataRef ? getCellValue(cellLabelDatapoint, cellLabelData.dataRef, tsData) : cellValue; - const cellLabel = cellLabelData && (typeof cellLabelValue === 'number') ? formatCellValue(cellLabelData, cellLabelValue) : null; + const cellLabelMappedValue = cellLabelData?.valueMappings ? valueMapping(cellLabelData.valueMappings, cellLabelValue) : null; + const cellLabel = cellLabelMappedValue || (cellLabelData && (typeof cellLabelValue === 'number') ? formatCellValue(cellLabelData, cellLabelValue) : null); const cellFillColorData = cellData.cellProps.fillColor; const cellFillColorDatapoint = cellFillColorData?.datapoint; diff --git a/src/test/SvgUpdater.test.tsx b/src/test/SvgUpdater.test.tsx index 001e803..9f2a631 100644 --- a/src/test/SvgUpdater.test.tsx +++ b/src/test/SvgUpdater.test.tsx @@ -1,5 +1,5 @@ -import { PanelConfigCellFlowAnimation } from 'components/Config'; -import { getFlowAnimationState } from 'components/SvgUpdater' +import { FlowValueMapping, PanelConfigCellFlowAnimation } from 'components/Config'; +import { getFlowAnimationState, valueMapping } from 'components/SvgUpdater' test('coherent_min_max', () => { let fad: PanelConfigCellFlowAnimation = { @@ -141,3 +141,31 @@ test('zero', () => { animState = getFlowAnimationState(fad, 5); expect(animState.durationSecs).toEqual(0); }); + +test('valuemapping', () => { + let fvm: FlowValueMapping[] = [ + {valid: true, value: -3, valueMin: undefined, valueMax: undefined, text: 'val_-3'}, + {valid: true, value: 0, valueMin: undefined, valueMax: undefined, text: 'val_0'}, + {valid: true, value: 3, valueMin: undefined, valueMax: undefined, text: 'val_3'}, + {valid: true, value: undefined, valueMin: 10, valueMax: 20, text: 'hello1'}, + {valid: true, value: undefined, valueMin: 21, valueMax: 30, text: 'hello2'}, + {valid: true, value: undefined, valueMin: 1000, valueMax: undefined, text: 'high'}, + {valid: true, value: undefined, valueMin: undefined, valueMax: -1000, text: 'low'}, + ]; + expect(valueMapping(fvm, -4)).toEqual(null); + expect(valueMapping(fvm, -3)).toEqual('val_-3'); + expect(valueMapping(fvm, 0)).toEqual('val_0'); + expect(valueMapping(fvm, 3)).toEqual('val_3'); + expect(valueMapping(fvm, null)).toEqual(null); + expect(valueMapping(fvm, -1100)).toEqual('low'); + expect(valueMapping(fvm, 1100)).toEqual('high'); +}); + +test('valuemappingnocriteria', () => { + let fvm: FlowValueMapping[] = [ + {valid: true, value: undefined, valueMin: undefined, valueMax: undefined, text: 'custom'}, + ]; + expect(valueMapping(fvm, -4)).toEqual('custom'); + expect(valueMapping(fvm, 0)).toEqual('custom'); + expect(valueMapping(fvm, 4)).toEqual('custom'); +}); \ No newline at end of file diff --git a/yaml_defs/panelConfig.yaml b/yaml_defs/panelConfig.yaml index b9f809f..2def1c0 100644 --- a/yaml_defs/panelConfig.yaml +++ b/yaml_defs/panelConfig.yaml @@ -167,6 +167,28 @@ cells: # undefined it will default to the value specified at the panel level in cellLabelDecimalPoints. decimalPoints: 0 + # Version 1.11.0 onwards: valueMappings is an array of 'match-criteria -> text' that allows you to + # display custom values. The array is iterated in order and breaks out on first match. If no match + # is found value formatting (with units) continues as normal. + # + # value: [optional] value must match this term. + # valueMin: [optional] value must match or exceed this term. + # valueMax: [optional] value must match or be under this term. + # text: text to substitute for the value when a match is made. + # + # Criteria can be specified as an exact match, a range or just a min or just a max. The min/max + # terms will be ignored if an exact match is also specified. + valueMappings: + - {value: null, text: "value is null"} + - {value: 3, text: "value is 3"} + - {valueMax: 300, text: "value is low"} + - {valueMin: 300, valueMax: 600, text: "value in range"} + - {valueMin: 600, text: "value is high"} + + # Version 1.11.0 onwards: valueMappings (see above) can also be specified in site config. Use + # this term to reference that set. + valueMappingsRef: "dbTrans" + # The label color field defines how the text in the cell should be colored based on the # time-series value. If no adjustment is wanted the field should be omitted. labelColor: diff --git a/yaml_defs/siteConfig.yaml b/yaml_defs/siteConfig.yaml index 34e223f..0fbacd1 100644 --- a/yaml_defs/siteConfig.yaml +++ b/yaml_defs/siteConfig.yaml @@ -34,7 +34,7 @@ colors: # These are refenced from panelConfig via the 'thresholdsRef' field. # i.e. in panelConfig you see: thresholdsRef: "queueCount". -# The fields inside a link are explained in the panelConfig documentation. +# The fields inside are explained in the panelConfig documentation. thresholds: queuecount: - {color: "green", level: 10} @@ -44,3 +44,16 @@ thresholds: - {color: "green", level: 200} - {color: "amber", level: 300} - {color: "red", level: 1500} + +# Version 1.11.0 onwards: These are refenced from panelConfig via the 'valueMappingsRef' field. +# i.e. in panelConfig you see: valueMappingsRef: "dbTrans". +# The fields inside are explained in the panelConfig documentation. +valueMappings: + dbTrans: + - {valueMax: 300, text: "low"} + - {valueMin: 300, valueMax: 600, text: "medium"} + - {valueMin: 600, text: "high"} + bypassValve: + - {value: 0, text: "auto"} + - {value: 1, text: "open"} + - {value: 2, text: "closed"}