From 65e9162a96b55617c95d382dee8a25bcb4e1ae56 Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 13:17:08 +0300 Subject: [PATCH 1/6] thresholds and value formatters --- src/models/options.ts | 85 ++++++++++++++++++++++++++----------------- src/module.ts | 2 +- src/types.ts | 15 ++++++++ 3 files changed, 68 insertions(+), 34 deletions(-) diff --git a/src/models/options.ts b/src/models/options.ts index 4b071b9..a07daaa 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,4 +1,5 @@ -import { PanelOptions, Aggregation } from 'types'; +import _ from 'lodash'; +import { PanelOptions, Aggregation, Threshold } from 'types'; import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../utils'; @@ -6,64 +7,82 @@ import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../utils'; export class Options { private minValue: number | undefined; private maxValue: number | undefined; + private thresholds: { value: number, color: string }[] = []; constructor(private grafanaSeriesList: any[], private grafanaOptions: PanelOptions) { this._setMin(); this._setMax(); + this._setThresholds(); } - private _setMin(): any { - if (!this.grafanaOptions.gauge.min.metricName) { + private _setMin(): void { + if (!this.grafanaOptions.gauge.min.useMetric) { this.minValue = this.grafanaOptions.gauge.min.value; return; } - const filteredSeries = filterMetricListByAlias( - this.grafanaSeriesList, - this.grafanaOptions.gauge.min.metricName, - 'Min' - ); - const serie = filteredSeries[0]; - // Last value for now - const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST); + const aggregatedValue = this.getLastValueFromMetrics(this.grafanaOptions.gauge.min.metricName, 'Min'); this.minValue = aggregatedValue ? aggregatedValue : undefined; } - private _setMax(): any { - if (!this.grafanaOptions.gauge.max.metricName) { + private _setMax(): void { + if (!this.grafanaOptions.gauge.max.useMetric) { this.maxValue = this.grafanaOptions.gauge.max.value; return; } - const filteredSeries = filterMetricListByAlias( - this.grafanaSeriesList, - this.grafanaOptions.gauge.max.metricName, - 'Max' - ); - const serie = filteredSeries[0]; - // Last value for now - const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST); + const aggregatedValue = this.getLastValueFromMetrics(this.grafanaOptions.gauge.max.metricName, 'Max'); this.maxValue = aggregatedValue ? aggregatedValue : undefined; } + private _setThresholds(): void { + if (_.isEmpty(this.grafanaOptions.gauge.thresholds.thresholds)) { + return; + } + for (let [idx, threshold] of this.grafanaOptions.gauge.thresholds.thresholds.entries()) { + this._setThreshold(threshold, idx); + } + } + + private _setThreshold(threshold: Threshold, idx: number): void { + const value = threshold.useMetric ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx}`) : threshold.value; + if(value === null || value === undefined) { + // TODO: may be throw an error + return; + } + this.thresholds.push({ + value, + color: threshold.color + }); + } + + private _valueFormatter(value: number): string { + const decimals = this.grafanaOptions.gauge.decimals || 2; + return `${value.toFixed(decimals)} ${this.grafanaOptions.gauge.unit}`; + } + getChartwerkOptions(): any { console.log('opt', this.maxValue, this.minValue); return { maxValue: this.maxValue, minValue: this.minValue, - valueFormatter: (val: any) => val.toFixed(2), - defaultColor: 'green', + valueFormatter: (val: number) => this._valueFormatter(val), + defaultColor: this.grafanaOptions.gauge.thresholds.defaultColor, + valueArcBackgroundColor: this.grafanaOptions.gauge.thresholds.arcBackground, reversed: this.grafanaOptions.gauge.reversed, - stops: [ - { - color: 'green', - value: 100, - }, - { - color: 'orange', - value: 140, - }, - ], + stops: this.thresholds, + valueFontSize: this.grafanaOptions.gauge.valueSize, // @ts-ignore icons: [{ src: 'https://cityhost.ua/upload_img/blog5ef308ea5529c_trash2-01.jpg', position: 'middle', size: 30 }], }; } + + getLastValueFromMetrics(metricName: string | undefined, optionName: string): number | null { + const filteredSeries = filterMetricListByAlias( + this.grafanaSeriesList, + metricName, + optionName + ); + const serie = filteredSeries[0]; + // Last value for now + return getAggregatedValueFromSerie(serie, Aggregation.LAST); + } } diff --git a/src/module.ts b/src/module.ts index af79332..f73279f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -87,7 +87,7 @@ export const plugin = new PanelPlugin(Panel).setPanelOptions((buil showIf: (config) => config.visualizationType === Pod.GAUGE, }) .addSliderInput({ - path: 'gauge.size', + path: 'gauge.valueSize', defaultValue: 20, name: 'Size (px)', settings: { diff --git a/src/types.ts b/src/types.ts index 923853c..88dfaec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,15 @@ export interface PanelOptions { min: ExtremumOptions; max: ExtremumOptions; value: ExtremumOptions; + valueSize: number; reversed: boolean; + decimals?: number; + unit?: string; + thresholds: { + arcBackground: string; + defaultColor: string; + thresholds: Threshold[]; + } }; } @@ -35,3 +43,10 @@ export enum Aggregation { MAX = 'max', LAST = 'last', } + +export type Threshold = { + useMetric: boolean; + value: number; + metricName: string; + color: string; +} From de0011105306ebe34c398864ba0740d423657c77 Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 14:17:49 +0300 Subject: [PATCH 2/6] minor UI update --- src/components/editors/ThresholdsEditor.tsx | 5 +++-- src/components/editors/UseMetricEditor.tsx | 1 + src/models/options.ts | 14 ++++++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/editors/ThresholdsEditor.tsx b/src/components/editors/ThresholdsEditor.tsx index f31df00..aacfc10 100644 --- a/src/components/editors/ThresholdsEditor.tsx +++ b/src/components/editors/ThresholdsEditor.tsx @@ -73,7 +73,7 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro const styles = getStyles(theme.v1); return (
- + - + onThresholdFieldChange(thresholdIdx, 'value', (evt.target as any).value)} /> diff --git a/src/components/editors/UseMetricEditor.tsx b/src/components/editors/UseMetricEditor.tsx index 37f7de0..cb2b248 100644 --- a/src/components/editors/UseMetricEditor.tsx +++ b/src/components/editors/UseMetricEditor.tsx @@ -53,6 +53,7 @@ export function UseMetricEditor({ onChange, value, context }: StandardEditorProp onFieldChange('value', (evt.target as any).value)} /> )} diff --git a/src/models/options.ts b/src/models/options.ts index a07daaa..828fb6b 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,8 +1,13 @@ -import _ from 'lodash'; import { PanelOptions, Aggregation, Threshold } from 'types'; import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../utils'; +import { getValueFormat } from '@grafana/data'; + +import _ from 'lodash'; + + + // Convert Grafana options into Chartwerk Gauge options export class Options { private minValue: number | undefined; @@ -43,7 +48,7 @@ export class Options { } private _setThreshold(threshold: Threshold, idx: number): void { - const value = threshold.useMetric ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx}`) : threshold.value; + const value = threshold.useMetric ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx + 1}`) : threshold.value; if(value === null || value === undefined) { // TODO: may be throw an error return; @@ -55,8 +60,9 @@ export class Options { } private _valueFormatter(value: number): string { - const decimals = this.grafanaOptions.gauge.decimals || 2; - return `${value.toFixed(decimals)} ${this.grafanaOptions.gauge.unit}`; + const suffix = getValueFormat(this.grafanaOptions.gauge.unit)(0)?.suffix || ''; + const decimals = _.isNumber(this.grafanaOptions.gauge.decimals) ? this.grafanaOptions.gauge.decimals : 2; + return `${value.toFixed(decimals)} ${suffix}`; } getChartwerkOptions(): any { From 9a3315d1a7ab96576aca955f1a9c34ed3a1a3317 Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 16:15:07 +0300 Subject: [PATCH 3/6] path icons options --- src/components/editors/IconsEditor.tsx | 27 ++------- src/models/options.ts | 83 ++++++++++++++++++++++++-- src/models/series.ts | 2 +- src/types.ts | 20 ++++++- src/utils.ts | 6 +- 5 files changed, 107 insertions(+), 31 deletions(-) diff --git a/src/components/editors/IconsEditor.tsx b/src/components/editors/IconsEditor.tsx index 9aee9cd..0617345 100644 --- a/src/components/editors/IconsEditor.tsx +++ b/src/components/editors/IconsEditor.tsx @@ -1,4 +1,4 @@ -import { IconPosition } from 'types'; +import { IconPosition, Icon, Condition } from 'types'; import { GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data'; import { @@ -18,23 +18,6 @@ import React from 'react'; import * as _ from 'lodash'; -type IconConfig = { - position: IconPosition; - url: string; - metrics: string[]; - conditions: Condition[]; - values: number[]; - size: number; -}; - -enum Condition { - EQUAL = '=', - GREATER = '>', - LESS = '<', - GREATER_OR_EQUAL = '>=', - LESS_OR_EQUAL = '<=', -} - const positionOptions: Array> = [ { label: 'Upper-Left', @@ -73,7 +56,7 @@ const conditionOptions: Array> = [ }, ]; -const DEFAULT_ICON: IconConfig = { +const DEFAULT_ICON: Icon = { position: IconPosition.UPPER_LEFT, url: '', size: 40, @@ -86,7 +69,7 @@ const fieldNamePickerSettings = { settings: { width: 24 }, } as any; -export function IconsEditor({ onChange, value, context }: StandardEditorProps) { +export function IconsEditor({ onChange, value, context }: StandardEditorProps) { const icons = value; const addIcon = () => { @@ -113,14 +96,14 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps { + const onIconFieldChange = (iconIdx: number, field: keyof Icon, value: any) => { // @ts-ignore icons[iconIdx][field] = value; onChange(icons); }; - const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => { + const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof Icon, value: any) => { // @ts-ignore icons[iconIdx][field][conditionIdx] = value; diff --git a/src/models/options.ts b/src/models/options.ts index 828fb6b..642250f 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,4 +1,4 @@ -import { PanelOptions, Aggregation, Threshold } from 'types'; +import { PanelOptions, Aggregation, Threshold, Icon, IconPosition, Condition } from 'types'; import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../utils'; @@ -13,11 +13,13 @@ export class Options { private minValue: number | undefined; private maxValue: number | undefined; private thresholds: { value: number, color: string }[] = []; + private icons: { src: string, position: string, size: number}[] = []; constructor(private grafanaSeriesList: any[], private grafanaOptions: PanelOptions) { this._setMin(); this._setMax(); this._setThresholds(); + this._setIcons(); } private _setMin(): void { @@ -65,8 +67,81 @@ export class Options { return `${value.toFixed(decimals)} ${suffix}`; } + private _setIcons(): void { + if (_.isEmpty(this.grafanaOptions.gauge.icons)) { + return; + } + for (let [idx, icon] of this.grafanaOptions.gauge.icons.entries()) { + this._setIcon(icon, idx); + } + } + + private _setIcon(icon: Icon, idx: number): void { + if (!this._areIconConditionsFulfilled(icon, idx)) { + return; + } + this.icons.push({ + src: icon.url, + size: icon.size, + position: this._getChartwerkIconPosition(icon.position), + }); + } + + private _areIconConditionsFulfilled(icon: Icon, iconIdx: number): boolean { + if(_.isEmpty(icon.metrics)) { + return true; + } + + // check each condition and return false if something goes wrong + for (let [conditionIdx, metric] of icon.metrics.entries()) { + const value = this.getLastValueFromMetrics(metric, `Icon ${iconIdx + 1}, Condition ${conditionIdx}`); + if(value === null || value === undefined) { + // TODO: may be throw an error + return false; + } + if (!this.checkIconCondition(value, icon.values[conditionIdx], icon.conditions[conditionIdx])) { + return false; + } + } + + return true; + } + + private checkIconCondition(metricValue: number, inputValue: number, condition: Condition): boolean { + if (inputValue === undefined || inputValue === null) { + return true; + } + switch (condition) { + case Condition.EQUAL: + return metricValue === inputValue; + case Condition.GREATER: + return metricValue > inputValue; + case Condition.GREATER_OR_EQUAL: + return metricValue >= inputValue; + case Condition.LESS: + return metricValue < inputValue; + case Condition.LESS_OR_EQUAL: + return metricValue <= inputValue; + default: + throw new Error(`Unknown condition: ${condition}`); + } + } + + private _getChartwerkIconPosition(position: IconPosition): string { + // TODO: use chartwerk types + switch (position) { + case IconPosition.MIDDLE: + return 'middle'; + case IconPosition.UPPER_LEFT: + return 'left'; + case IconPosition.UPPER_RIGHT: + return 'right'; + default: + throw new Error(`Unknown Icon Position ${position}`); + } + } + getChartwerkOptions(): any { - console.log('opt', this.maxValue, this.minValue); return { maxValue: this.maxValue, minValue: this.minValue, @@ -76,12 +151,12 @@ export class Options { reversed: this.grafanaOptions.gauge.reversed, stops: this.thresholds, valueFontSize: this.grafanaOptions.gauge.valueSize, - // @ts-ignore - icons: [{ src: 'https://cityhost.ua/upload_img/blog5ef308ea5529c_trash2-01.jpg', position: 'middle', size: 30 }], + icons: this.icons, }; } getLastValueFromMetrics(metricName: string | undefined, optionName: string): number | null { + // optionName -> helper in Error, mb use option path instead const filteredSeries = filterMetricListByAlias( this.grafanaSeriesList, metricName, diff --git a/src/models/series.ts b/src/models/series.ts index b1956a9..a2b7b29 100644 --- a/src/models/series.ts +++ b/src/models/series.ts @@ -29,7 +29,7 @@ export class Series { return { target: serie.alias, color: serie.color, - datapoints: _.map(serie.datapoints, (row) => _.reverse(row)), + datapoints: _.map(serie.datapoints, (row) => _.reverse(_.clone(row))), alias: serie.label, }; }); diff --git a/src/types.ts b/src/types.ts index 88dfaec..a0a2e94 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,8 @@ export interface PanelOptions { arcBackground: string; defaultColor: string; thresholds: Threshold[]; - } + }; + icons: Icon[]; }; } @@ -50,3 +51,20 @@ export type Threshold = { metricName: string; color: string; } + +export type Icon = { + conditions: Condition[]; + metrics: string[]; + values: number[]; + position: IconPosition; + size: number; + url: string; +}; + +export enum Condition { + EQUAL = '=', + GREATER = '>', + LESS = '<', + GREATER_OR_EQUAL = '>=', + LESS_OR_EQUAL = '<=', +} diff --git a/src/utils.ts b/src/utils.ts index dff1836..014f99e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,8 +13,9 @@ export function filterMetricListByAlias(list: any[], alias: string | undefined, return filteredSeries; } -export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST): number | null { +export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST, valueIdx: 0 | 1 = 0): number | null { // series types { datapoints: [number, number][]} + // valueIdx === 0 for Grafana series, valueIdx === 1 for Chartwerk series if (serie === undefined) { return null; } @@ -24,9 +25,8 @@ export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregatio switch (aggregation) { case Aggregation.LAST: const lastRow = _.last(serie.datapoints as Array<[number, number]>); - // [0] because it is Grafan series. So 0 idx for values, 1 idx for timestamps // @ts-ignore - return !_.isEmpty(lastRow) ? lastRow[0] : null; + return !_.isEmpty(lastRow) ? lastRow[valueIdx] : null; default: throw new Error(`Unknown aggregation type: ${aggregation}`); } From ffc90a64b52d4e7bfc0d9fa0a379ada4008f4fae Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 16:19:39 +0300 Subject: [PATCH 4/6] remove logs --- src/models/options.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/options.ts b/src/models/options.ts index 642250f..1db96d9 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -94,7 +94,7 @@ export class Options { // check each condition and return false if something goes wrong for (let [conditionIdx, metric] of icon.metrics.entries()) { - const value = this.getLastValueFromMetrics(metric, `Icon ${iconIdx + 1}, Condition ${conditionIdx}`); + const value = this.getLastValueFromMetrics(metric, `Icon ${iconIdx + 1}, Condition ${conditionIdx + 1}`); if(value === null || value === undefined) { // TODO: may be throw an error return false; From d9e7b87080f5f8f4da52a278c26040d8358e9166 Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 16:42:26 +0300 Subject: [PATCH 5/6] link click --- src/components/Panel.tsx | 13 +++++++++++++ src/module.ts | 6 ++++++ src/types.ts | 5 +++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 3cba27f..08d45cb 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -29,13 +29,26 @@ export function Panel({ options, data, width, height, timeZone, timeRange, onCha const pod = new ChartwerkGaugePod((chartContainer as any).current, series, chartwerkOptions); pod.render(); }); + + const isLinkActive = !_.isEmpty(options.gauge.link); + const chartClickHandler = (event: React.MouseEvent) => { + event.preventDefault(); + console.log('click', options); + if (!isLinkActive) { + return; + } + window.open(options.gauge.link, "_self"); + }; + return (
); } diff --git a/src/module.ts b/src/module.ts index f73279f..0ecec60 100644 --- a/src/module.ts +++ b/src/module.ts @@ -128,5 +128,11 @@ export const plugin = new PanelPlugin(Panel).setPanelOptions((buil }, showIf: (config) => config.visualizationType === Pod.GAUGE, editor: ThresholdsEditor as any, + }) + .addTextInput({ + path: 'gauge.link', + name: '', + category: ['Link'], + showIf: (config) => config.visualizationType === Pod.GAUGE, }); }); diff --git a/src/types.ts b/src/types.ts index a0a2e94..0957663 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,14 +6,15 @@ export interface PanelOptions { value: ExtremumOptions; valueSize: number; reversed: boolean; - decimals?: number; - unit?: string; thresholds: { arcBackground: string; defaultColor: string; thresholds: Threshold[]; }; icons: Icon[]; + decimals?: number; + unit?: string; + link?: string; }; } From 1983d31aec3638b203423b811679626c735d63c5 Mon Sep 17 00:00:00 2001 From: vargburz Date: Wed, 11 May 2022 16:53:02 +0300 Subject: [PATCH 6/6] link fixes --- src/components/Panel.tsx | 5 +- src/components/editors/IconsEditor.tsx | 11 +- src/components/editors/NotSupportedText.tsx | 2 +- src/components/editors/ThresholdsEditor.tsx | 3 +- src/components/editors/UseMetricEditor.tsx | 12 +- src/models/options.ts | 24 +- src/module.ts | 241 ++++++++++---------- src/types.ts | 2 +- src/utils.ts | 6 +- 9 files changed, 147 insertions(+), 159 deletions(-) diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 08d45cb..50213a2 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -33,11 +33,10 @@ export function Panel({ options, data, width, height, timeZone, timeRange, onCha const isLinkActive = !_.isEmpty(options.gauge.link); const chartClickHandler = (event: React.MouseEvent) => { event.preventDefault(); - console.log('click', options); if (!isLinkActive) { return; } - window.open(options.gauge.link, "_self"); + window.open(options.gauge.link, '_self'); }; return ( @@ -46,7 +45,7 @@ export function Panel({ options, data, width, height, timeZone, timeRange, onCha className={css` width: ${width}px; height: ${height}px; - cursor: ${isLinkActive ? 'pointer' : 'default'} + cursor: ${isLinkActive ? 'pointer' : 'default'}; `} onClick={chartClickHandler} >
diff --git a/src/components/editors/IconsEditor.tsx b/src/components/editors/IconsEditor.tsx index 0617345..b989d7f 100644 --- a/src/components/editors/IconsEditor.tsx +++ b/src/components/editors/IconsEditor.tsx @@ -120,11 +120,7 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps { return (
- removeIcon(iconIdx)} - tooltip="Delete Icon" - > + removeIcon(iconIdx)} tooltip="Delete Icon">
{ return ( -
- +
+ { margin-bottom: ${theme.spacing.sm}; `, }; - }); diff --git a/src/components/editors/NotSupportedText.tsx b/src/components/editors/NotSupportedText.tsx index 22c4432..3b50408 100644 --- a/src/components/editors/NotSupportedText.tsx +++ b/src/components/editors/NotSupportedText.tsx @@ -1,5 +1,5 @@ import React from 'react'; export function NotSupportedText() { - return
To be supported soon...
+ return
To be supported soon...
; } diff --git a/src/components/editors/ThresholdsEditor.tsx b/src/components/editors/ThresholdsEditor.tsx index aacfc10..354b4d3 100644 --- a/src/components/editors/ThresholdsEditor.tsx +++ b/src/components/editors/ThresholdsEditor.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { css } from 'emotion'; import * as _ from 'lodash'; - interface Props { defaultColor: string; arcBackground: string; @@ -156,7 +155,7 @@ interface ThresholdsEditorStyles { const getStyles = stylesFactory((theme: GrafanaTheme): ThresholdsEditorStyles => { return { deleteButton: css` - margin-right: 0px!important; + margin-right: 0px !important; `, }; }); diff --git a/src/components/editors/UseMetricEditor.tsx b/src/components/editors/UseMetricEditor.tsx index cb2b248..4018e26 100644 --- a/src/components/editors/UseMetricEditor.tsx +++ b/src/components/editors/UseMetricEditor.tsx @@ -1,18 +1,12 @@ import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker'; import { StandardEditorProps } from '@grafana/data'; -import { - HorizontalGroup, - InlineField, - InlineSwitch, - Input, -} from '@grafana/ui'; +import { HorizontalGroup, InlineField, InlineSwitch, Input } from '@grafana/ui'; import React from 'react'; import * as _ from 'lodash'; - type UseMetricConfig = { useMetric: boolean; value?: number; @@ -31,7 +25,7 @@ export function UseMetricEditor({ onChange, value, context }: StandardEditorProp config[field] = value; onChange(config); - } + }; return ( @@ -59,5 +53,5 @@ export function UseMetricEditor({ onChange, value, context }: StandardEditorProp )} - ) + ); } diff --git a/src/models/options.ts b/src/models/options.ts index 1db96d9..f150d29 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -6,14 +6,12 @@ import { getValueFormat } from '@grafana/data'; import _ from 'lodash'; - - // Convert Grafana options into Chartwerk Gauge options export class Options { private minValue: number | undefined; private maxValue: number | undefined; - private thresholds: { value: number, color: string }[] = []; - private icons: { src: string, position: string, size: number}[] = []; + private thresholds: Array<{ value: number; color: string }> = []; + private icons: Array<{ src: string; position: string; size: number }> = []; constructor(private grafanaSeriesList: any[], private grafanaOptions: PanelOptions) { this._setMin(); @@ -50,14 +48,16 @@ export class Options { } private _setThreshold(threshold: Threshold, idx: number): void { - const value = threshold.useMetric ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx + 1}`) : threshold.value; - if(value === null || value === undefined) { + const value = threshold.useMetric + ? this.getLastValueFromMetrics(threshold.metricName, `Threshold ${idx + 1}`) + : threshold.value; + if (value === null || value === undefined) { // TODO: may be throw an error return; } this.thresholds.push({ value, - color: threshold.color + color: threshold.color, }); } @@ -88,14 +88,14 @@ export class Options { } private _areIconConditionsFulfilled(icon: Icon, iconIdx: number): boolean { - if(_.isEmpty(icon.metrics)) { + if (_.isEmpty(icon.metrics)) { return true; } // check each condition and return false if something goes wrong for (let [conditionIdx, metric] of icon.metrics.entries()) { const value = this.getLastValueFromMetrics(metric, `Icon ${iconIdx + 1}, Condition ${conditionIdx + 1}`); - if(value === null || value === undefined) { + if (value === null || value === undefined) { // TODO: may be throw an error return false; } @@ -157,11 +157,7 @@ export class Options { getLastValueFromMetrics(metricName: string | undefined, optionName: string): number | null { // optionName -> helper in Error, mb use option path instead - const filteredSeries = filterMetricListByAlias( - this.grafanaSeriesList, - metricName, - optionName - ); + const filteredSeries = filterMetricListByAlias(this.grafanaSeriesList, metricName, optionName); const serie = filteredSeries[0]; // Last value for now return getAggregatedValueFromSerie(serie, Aggregation.LAST); diff --git a/src/module.ts b/src/module.ts index 0ecec60..4c0925e 100644 --- a/src/module.ts +++ b/src/module.ts @@ -9,130 +9,131 @@ import { UseMetricEditor } from './components/editors/UseMetricEditor'; import { PanelPlugin } from '@grafana/data'; - export const plugin = new PanelPlugin(Panel).setPanelOptions((builder) => { - return builder - .addRadio({ - path: 'visualizationType', - name: 'Pod', - category: ['Visualization'], - defaultValue: Pod.GAUGE, - settings: { - options: [ - { - label: 'Gauge', - value: Pod.GAUGE, - }, - { - label: 'Line', - value: Pod.LINE, - }, - { - label: 'Bar', - value: Pod.BAR, - }, - ], - }, - }) - .addCustomEditor({ - id: 'notSupportedText', - name: 'This visualization is not supported', - category: ['Visualization'], - path: '', - showIf: (config) => config.visualizationType !== Pod.GAUGE, - editor: NotSupportedText as any, - }) + return ( + builder + .addRadio({ + path: 'visualizationType', + name: 'Pod', + category: ['Visualization'], + defaultValue: Pod.GAUGE, + settings: { + options: [ + { + label: 'Gauge', + value: Pod.GAUGE, + }, + { + label: 'Line', + value: Pod.LINE, + }, + { + label: 'Bar', + value: Pod.BAR, + }, + ], + }, + }) + .addCustomEditor({ + id: 'notSupportedText', + name: 'This visualization is not supported', + category: ['Visualization'], + path: '', + showIf: (config) => config.visualizationType !== Pod.GAUGE, + editor: NotSupportedText as any, + }) - .addFieldNamePicker({ - name: 'Value', - path: 'gauge.value.metricName', - category: ['Extremum'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }) - // TODO: defaults? - .addCustomEditor({ - id: 'min', - name: 'Min', - path: 'gauge.min', - category: ['Extremum'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - editor: UseMetricEditor as any, - }). - addCustomEditor({ - id: 'max', - name: 'Max', - path: 'gauge.max', - category: ['Extremum'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - editor: UseMetricEditor as any, - }) + .addFieldNamePicker({ + name: 'Value', + path: 'gauge.value.metricName', + category: ['Extremum'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) + // TODO: defaults? + .addCustomEditor({ + id: 'min', + name: 'Min', + path: 'gauge.min', + category: ['Extremum'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + editor: UseMetricEditor as any, + }) + .addCustomEditor({ + id: 'max', + name: 'Max', + path: 'gauge.max', + category: ['Extremum'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + editor: UseMetricEditor as any, + }) - // note: `gauge.unit` will contain unit name, not it's string representation - // to format value with unit, use `getValueFormat` function from `@grafana/data` - .addUnitPicker({ - path: 'gauge.unit', - name: 'Unit', - category: ['Value Format'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }) - .addNumberInput({ - path: 'gauge.decimals', - name: 'Decimals', - settings: { - placeholder: 'auto', - min: 0, - max: 5, - }, - category: ['Value Format'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }) - .addSliderInput({ - path: 'gauge.valueSize', - defaultValue: 20, - name: 'Size (px)', - settings: { - min: 1, - max: 50, - }, - category: ['Value Format'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }) + // note: `gauge.unit` will contain unit name, not it's string representation + // to format value with unit, use `getValueFormat` function from `@grafana/data` + .addUnitPicker({ + path: 'gauge.unit', + name: 'Unit', + category: ['Value Format'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) + .addNumberInput({ + path: 'gauge.decimals', + name: 'Decimals', + settings: { + placeholder: 'auto', + min: 0, + max: 5, + }, + category: ['Value Format'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) + .addSliderInput({ + path: 'gauge.valueSize', + defaultValue: 20, + name: 'Size (px)', + settings: { + min: 1, + max: 50, + }, + category: ['Value Format'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) - .addBooleanSwitch({ - path: 'gauge.reversed', - name: 'Reversed', - defaultValue: false, - category: ['Direction'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }) + .addBooleanSwitch({ + path: 'gauge.reversed', + name: 'Reversed', + defaultValue: false, + category: ['Direction'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) - .addCustomEditor({ - id: 'icons', - path: 'gauge.icons', - name: 'Icons', - category: ['Icons'], - defaultValue: [], - showIf: (config) => config.visualizationType === Pod.GAUGE, - editor: IconsEditor as any, - }) + .addCustomEditor({ + id: 'icons', + path: 'gauge.icons', + name: 'Icons', + category: ['Icons'], + defaultValue: [], + showIf: (config) => config.visualizationType === Pod.GAUGE, + editor: IconsEditor as any, + }) - .addCustomEditor({ - id: 'thresholds', - path: 'gauge.thresholds', - name: 'Thresholds', - category: ['Thresholds'], - defaultValue: { - defaultColor: '#37872d', - arcBackground: 'rgba(38, 38, 38, 0.1)', - thresholds: [], - }, - showIf: (config) => config.visualizationType === Pod.GAUGE, - editor: ThresholdsEditor as any, - }) - .addTextInput({ - path: 'gauge.link', - name: '', - category: ['Link'], - showIf: (config) => config.visualizationType === Pod.GAUGE, - }); + .addCustomEditor({ + id: 'thresholds', + path: 'gauge.thresholds', + name: 'Thresholds', + category: ['Thresholds'], + defaultValue: { + defaultColor: '#37872d', + arcBackground: 'rgba(38, 38, 38, 0.1)', + thresholds: [], + }, + showIf: (config) => config.visualizationType === Pod.GAUGE, + editor: ThresholdsEditor as any, + }) + .addTextInput({ + path: 'gauge.link', + name: '', + category: ['Link'], + showIf: (config) => config.visualizationType === Pod.GAUGE, + }) + ); }); diff --git a/src/types.ts b/src/types.ts index 0957663..cf3f960 100644 --- a/src/types.ts +++ b/src/types.ts @@ -51,7 +51,7 @@ export type Threshold = { value: number; metricName: string; color: string; -} +}; export type Icon = { conditions: Condition[]; diff --git a/src/utils.ts b/src/utils.ts index 014f99e..007f8d2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,7 +13,11 @@ export function filterMetricListByAlias(list: any[], alias: string | undefined, return filteredSeries; } -export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST, valueIdx: 0 | 1 = 0): number | null { +export function getAggregatedValueFromSerie( + serie: any, + aggregation = Aggregation.LAST, + valueIdx: 0 | 1 = 0 +): number | null { // series types { datapoints: [number, number][]} // valueIdx === 0 for Grafana series, valueIdx === 1 for Chartwerk series if (serie === undefined) {