diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index 9363cdf..fcb68dc 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -3,9 +3,12 @@ import { Series } from '../models/series'; import { PanelOptions } from '../types'; +import { DataProcessor } from '../grafana/data_processor'; + import { ChartwerkGaugePod } from '@chartwerk/gauge-pod'; import { PanelProps } from '@grafana/data'; +import { PanelData, TimeRange } from '@grafana/data'; import React, { useRef } from 'react'; import { css } from 'emotion'; @@ -15,9 +18,10 @@ interface Props extends PanelProps {} export function Panel({ options, data, width, height, timeZone, timeRange, onChangeTimeRange }: Props) { console.log('options', options); - const series = new Series(data, timeRange, options.gauge.value).getChartwerkSeries(); + const grafanaSeriesList = getGrafanaSeriesList(data, timeRange); + const series = new Series(grafanaSeriesList, options.gauge.value).getChartwerkSeries(); console.log('series', series); - const chartwerkOptions = new Options(options).getChartwerkOptions(); + const chartwerkOptions = new Options(grafanaSeriesList, options).getChartwerkOptions(); let chartContainer = useRef(null); // we request animation frame here because we need an existing DOM-element at the moment we render the pod @@ -40,3 +44,11 @@ export function Panel({ options, data, width, height, timeZone, timeRange, onCha > ); } + +function getGrafanaSeriesList(grafanaData: PanelData, timeRange: TimeRange): any[] { + const processor = new DataProcessor({}); + return processor.getSeriesList({ + dataList: grafanaData.series, + range: timeRange, + }); +} diff --git a/src/models/options.ts b/src/models/options.ts index 01d32d3..bf572f9 100644 --- a/src/models/options.ts +++ b/src/models/options.ts @@ -1,15 +1,49 @@ -import { PanelOptions } from 'types'; +import { PanelOptions, Aggregation } from 'types'; -// Convert Grafana options into Chartwerk options +import { filterMetricListByAlias, getAggregatedValueFromSerie } from '../utils'; + +// Convert Grafana options into Chartwerk Gauge options export class Options { - constructor(private grafanaOptions: PanelOptions) {} + private minValue: number | undefined; + private maxValue: number | undefined; + + constructor(private grafanaSeriesList: any[], private grafanaOptions: PanelOptions) { + this._setMin(); + this._setMax(); + } + + private _setMin(): any { + if(!this.grafanaOptions.gauge.min.metricName) { + 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); + this.minValue = aggregatedValue ? aggregatedValue : undefined; + } + + private _setMax(): any { + if(!this.grafanaOptions.gauge.max.metricName) { + 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); + this.maxValue = aggregatedValue ? aggregatedValue : undefined; + } public getChartwerkOptions(): any { + console.log('opt', this.maxValue, this.minValue); return { - maxValue: this.grafanaOptions.gauge.max.value || 0, - minValue: this.grafanaOptions.gauge.min.value || 0, + maxValue: this.maxValue, + minValue: this.minValue, valueFormatter: (val: any) => val.toFixed(2), defaultColor: 'green', + reversed: this.grafanaOptions.gauge.reversed, stops: [ { color: 'green', diff --git a/src/models/series.ts b/src/models/series.ts index 7949228..95cd99a 100644 --- a/src/models/series.ts +++ b/src/models/series.ts @@ -1,36 +1,23 @@ -import { PanelData, TimeRange } from '@grafana/data'; -import { DataProcessor } from '../grafana/data_processor'; import { ValueOptions } from 'types'; +import { filterMetricListByAlias } from '../utils'; + import * as _ from 'lodash'; // Convert Grafana series into Chartwerk series export class Series { - private processor; private _seriesList; private _selectedSerieName; - constructor(grafanaData: PanelData, timeRange: TimeRange, private gaugeValueOptions: ValueOptions) { + constructor(grafanaSeriesList: any, private gaugeValueOptions: ValueOptions) { if(_.isEmpty(this.gaugeValueOptions.metricName)) { - throw new Error(`Value or metric is not selected.`); + throw new Error(`Value: metric is not selected. [See options: Extremum -> Value]`); } - this._selectedSerieName = this.gaugeValueOptions.metricName; - this.processor = new DataProcessor({}); - const seriesList = this.processor.getSeriesList({ - dataList: grafanaData.series, - range: timeRange, - }); - const filteredSeries = _.filter(seriesList, serie => serie.alias === this._selectedSerieName); - if(filteredSeries.length === 0) { - throw new Error(`Can't find metric for ${this._selectedSerieName} name`); - } - if(filteredSeries.length > 1) { - throw new Error(`Get ${filteredSeries.length} metrics for ${this._selectedSerieName} name. Please choose one`); - } + const filteredSeries = filterMetricListByAlias(grafanaSeriesList, this._selectedSerieName, 'Value'); this._seriesList = this._updateSeriesListWithChartwerkParams(filteredSeries); } diff --git a/src/module.ts b/src/module.ts index 658e5e8..8f08043 100644 --- a/src/module.ts +++ b/src/module.ts @@ -81,7 +81,13 @@ export const plugin = new PanelPlugin(Panel).setPanelOptions((buil category: ['Extremum'], 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', diff --git a/src/types.ts b/src/types.ts index aed200c..f0427d2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ export interface PanelOptions { min: ExtremumOptions; max: ExtremumOptions; value: ExtremumOptions; + reversed: boolean; }; } @@ -28,3 +29,9 @@ export enum Pod { BAR = 'Bar', GAUGE = 'Gauge', } + +export enum Aggregation { + MIN = 'min', + MAX = 'max', + LAST = 'last' +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2075909 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,33 @@ +import { Aggregation } from './types'; + +import * as _ from 'lodash'; + +export function filterMetricListByAlias(list: any[], alias: string | undefined, option: string): any[] { + const filteredSeries = _.filter(list, serie => serie.alias === alias); + if(filteredSeries.length === 0) { + throw new Error(`${option}: Can't find metric for ${alias} name.`); + } + if(filteredSeries.length > 1) { + throw new Error(`${option}: Get ${filteredSeries.length} metrics for ${alias} name. Please choose one.`); + } + return filteredSeries; +} + +export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST): number | null { + // series types { datapoints: [number, number][]} + if(serie === undefined) { + return null; + } + if(serie.datapoints.length === 0) { + return null; + } + switch(aggregation) { + case Aggregation.LAST: + const lastRow = _.last((serie.datapoints as [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; + default: + throw new Error(`Unknown aggregation type: ${aggregation}`) + } +}