Browse Source

linting

merge-requests/6/merge
rozetko 3 years ago
parent
commit
14c7104e5f
  1. 6
      src/components/Panel.tsx
  2. 163
      src/components/editors/IconsEditor.tsx
  3. 188
      src/components/editors/ThresholdsEditor.tsx
  4. 16
      src/models/options.ts
  5. 4
      src/models/series.ts
  6. 7
      src/module.ts
  7. 10
      src/types.ts
  8. 16
      src/utils.ts

6
src/components/Panel.tsx

@ -27,11 +27,7 @@ export function Panel({ options, data, width, height, timeZone, timeRange, onCha
// we request animation frame here because we need an existing DOM-element at the moment we render the pod // we request animation frame here because we need an existing DOM-element at the moment we render the pod
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
// TODO: switch / case pod type // TODO: switch / case pod type
const pod = new ChartwerkGaugePod( const pod = new ChartwerkGaugePod((chartContainer as any).current, series, chartwerkOptions);
(chartContainer as any).current,
series,
chartwerkOptions
);
pod.render(); pod.render();
}); });
return ( return (

163
src/components/editors/IconsEditor.tsx

@ -1,7 +1,16 @@
import { IconPosition } from 'types'; import { IconPosition } from 'types';
import { GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data'; import { GrafanaTheme, SelectableValue, StandardEditorProps } from '@grafana/data';
import { Button, HorizontalGroup, IconButton, Input, RadioButtonGroup, Slider, stylesFactory, ThemeContext } from '@grafana/ui'; import {
Button,
HorizontalGroup,
IconButton,
Input,
RadioButtonGroup,
Slider,
stylesFactory,
ThemeContext,
} from '@grafana/ui';
import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker'; import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker';
import { css } from 'emotion'; import { css } from 'emotion';
@ -9,7 +18,6 @@ import React from 'react';
import * as _ from 'lodash'; import * as _ from 'lodash';
type IconConfig = { type IconConfig = {
position: IconPosition; position: IconPosition;
url: string; url: string;
@ -17,7 +25,7 @@ type IconConfig = {
conditions: Condition[]; conditions: Condition[];
values: number[]; values: number[];
size: number; size: number;
} };
enum Condition { enum Condition {
EQUAL = '=', EQUAL = '=',
@ -27,7 +35,6 @@ enum Condition {
LESS_OR_EQUAL = '<=', LESS_OR_EQUAL = '<=',
} }
const positionOptions: Array<SelectableValue<IconPosition>> = [ const positionOptions: Array<SelectableValue<IconPosition>> = [
{ {
label: 'Upper-Left', label: 'Upper-Left',
@ -73,7 +80,7 @@ const DEFAULT_ICON: IconConfig = {
metrics: [], metrics: [],
conditions: [], conditions: [],
values: [], values: [],
} };
const fieldNamePickerSettings = { const fieldNamePickerSettings = {
settings: { width: 24 }, settings: { width: 24 },
@ -83,18 +90,14 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ar
const icons = value; const icons = value;
const addIcon = () => { const addIcon = () => {
onChange( onChange(_.concat(icons, _.cloneDeep(DEFAULT_ICON)));
_.concat(icons, _.cloneDeep(DEFAULT_ICON))
);
}; };
const removeIcon = (idx: number) => { const removeIcon = (idx: number) => {
onChange( onChange(_.filter(icons, (icon, iconIdx) => iconIdx !== idx));
_.filter(icons, (icon, iconIdx) => iconIdx !== idx)
);
}; };
const addCondition = (iconIdx:number) => { const addCondition = (iconIdx: number) => {
icons[iconIdx].conditions.push(Condition.GREATER_OR_EQUAL); icons[iconIdx].conditions.push(Condition.GREATER_OR_EQUAL);
icons[iconIdx].metrics.push(''); icons[iconIdx].metrics.push('');
icons[iconIdx].values.push(0); icons[iconIdx].values.push(0);
@ -118,7 +121,7 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ar
}; };
const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => { const onConditionChange = (iconIdx: number, conditionIdx: number, field: keyof IconConfig, value: any) => {
console.log(value) console.log(value);
// @ts-ignore // @ts-ignore
icons[iconIdx][field][conditionIdx] = value; icons[iconIdx][field][conditionIdx] = value;
@ -128,72 +131,76 @@ export function IconsEditor({ onChange, value, context }: StandardEditorProps<Ar
return ( return (
<ThemeContext.Consumer> <ThemeContext.Consumer>
{(theme) => { {(theme) => {
const styles = getStyles(theme.v1); const styles = getStyles(theme.v1);
return ( return (
<div> <div>
<div className={styles.icons}> <div className={styles.icons}>
{ {icons.map((icon, iconIdx) => {
icons.map((icon, iconIdx) => { return (
return ( <div className={styles.icon}>
<div className={styles.icon}> <IconButton name="trash-alt" onClick={() => removeIcon(iconIdx)}></IconButton>
<IconButton name="trash-alt" onClick={() => removeIcon(iconIdx)}></IconButton> <Input
<Input type="url"
type="url" placeholder="Image URL"
placeholder="Image URL" value={icon.url}
value={icon.url} onChange={(evt) => onIconFieldChange(iconIdx, 'url', (evt.target as any).value)}
onChange={evt => onIconFieldChange(iconIdx, 'url', (evt.target as any).value)} />
/> <RadioButtonGroup
<RadioButtonGroup value={icon.position}
value={icon.position} options={positionOptions}
options={positionOptions} onChange={(newVal) => onIconFieldChange(iconIdx, 'position', newVal)}
onChange={newVal => onIconFieldChange(iconIdx, 'position', newVal)} />
/> <Slider
<Slider value={icon.size}
value={icon.size} min={1}
min={1} max={100}
max={100} step={1}
step={1} onAfterChange={(newVal) => onIconFieldChange(iconIdx, 'size', newVal)}
onAfterChange={newVal => onIconFieldChange(iconIdx, 'size', newVal)} />
/>
{icon.conditions.map((condition, conditionIdx) => {
{ return (
icon.conditions.map((condition, conditionIdx) => { <HorizontalGroup>
return ( <FieldNamePicker
<HorizontalGroup> context={context}
<FieldNamePicker value={icon.metrics[conditionIdx]}
context={context} onChange={(newVal: any) => onConditionChange(iconIdx, conditionIdx, 'metrics', newVal)}
value={icon.metrics[conditionIdx]} item={fieldNamePickerSettings}
onChange={(newVal: any) => onConditionChange(iconIdx, conditionIdx, 'metrics', newVal) } />
item={fieldNamePickerSettings} <RadioButtonGroup
/> value={icon.conditions[conditionIdx]}
<RadioButtonGroup options={conditionOptions}
value={icon.conditions[conditionIdx]} onChange={(newVal) => onConditionChange(iconIdx, conditionIdx, 'conditions', newVal)}
options={conditionOptions} />
onChange={newVal => onConditionChange(iconIdx, conditionIdx, 'conditions', newVal)} <Input
/> placeholder="value"
<Input value={icon.values[conditionIdx]}
placeholder="value" onChange={(evt) =>
value={icon.values[conditionIdx]} onConditionChange(iconIdx, conditionIdx, 'values', (evt.target as any).value)
onChange={evt => onConditionChange(iconIdx, conditionIdx, 'values', (evt.target as any).value)} }
/> />
<IconButton name="trash-alt" onClick={() => removeCondition(iconIdx, conditionIdx)}></IconButton> <IconButton
</HorizontalGroup> name="trash-alt"
) onClick={() => removeCondition(iconIdx, conditionIdx)}
}) ></IconButton>
} </HorizontalGroup>
<Button variant="secondary" onClick={() => addCondition(iconIdx)}>Add Condition</Button> );
</div> })}
) <Button variant="secondary" onClick={() => addCondition(iconIdx)}>
}) Add Condition
} </Button>
</div> </div>
<Button variant="secondary" onClick={addIcon}>Add Icon</Button> );
})}
</div> </div>
) <Button variant="secondary" onClick={addIcon}>
} Add Icon
} </Button>
</div>
);
}}
</ThemeContext.Consumer> </ThemeContext.Consumer>
) );
} }
interface IconsEditorStyles { interface IconsEditorStyles {
@ -218,5 +225,5 @@ const getStyles = stylesFactory((theme: GrafanaTheme): IconsEditorStyles => {
margin-bottom: 0; margin-bottom: 0;
} }
`, `,
} };
}); });

188
src/components/editors/ThresholdsEditor.tsx

@ -1,17 +1,25 @@
import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker'; import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker';
import { StandardEditorProps } from '@grafana/data'; import { StandardEditorProps } from '@grafana/data';
import { Button, ColorPicker, HorizontalGroup, IconButton, InlineField, InlineSwitch, Input, ThemeContext } from '@grafana/ui'; import {
Button,
ColorPicker,
HorizontalGroup,
IconButton,
InlineField,
InlineSwitch,
Input,
ThemeContext,
} from '@grafana/ui';
import React from 'react'; import React from 'react';
import * as _ from 'lodash'; import * as _ from 'lodash';
interface Props { interface Props {
defaultColor: string, defaultColor: string;
arcBackground: string, arcBackground: string;
thresholds: Array<ThresholdConfig> thresholds: Array<ThresholdConfig>;
} }
type ThresholdConfig = { type ThresholdConfig = {
@ -19,13 +27,13 @@ type ThresholdConfig = {
useMetric: boolean; useMetric: boolean;
value?: number; value?: number;
metricName?: string; metricName?: string;
} };
const DEFAULT_THRESHOLD: ThresholdConfig = { const DEFAULT_THRESHOLD: ThresholdConfig = {
color: '#378372d', color: '#378372d',
useMetric: false, useMetric: false,
value: 0 value: 0,
} };
const fieldNamePickerSettings = { const fieldNamePickerSettings = {
settings: { width: 24 }, settings: { width: 24 },
@ -37,17 +45,17 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
const addThreshold = () => { const addThreshold = () => {
config.thresholds.push(_.cloneDeep(DEFAULT_THRESHOLD)); config.thresholds.push(_.cloneDeep(DEFAULT_THRESHOLD));
onChange(config); onChange(config);
} };
const removeThreshold = (idx: number) => { const removeThreshold = (idx: number) => {
config.thresholds.splice(idx, 1); config.thresholds.splice(idx, 1);
onChange(config); onChange(config);
} };
const onFieldChange = (field: keyof Props, value: any) => { const onFieldChange = (field: keyof Props, value: any) => {
config[field] = value; config[field] = value;
onChange(config); onChange(config);
} };
const onThresholdFieldChange = (thresholdIdx: number, field: keyof ThresholdConfig, value: any) => { const onThresholdFieldChange = (thresholdIdx: number, field: keyof ThresholdConfig, value: any) => {
// @ts-ignore // @ts-ignore
@ -57,87 +65,85 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
return ( return (
<ThemeContext.Consumer> <ThemeContext.Consumer>
{ {() => {
() => { // const styles = getStyles(theme.v1);
// const styles = getStyles(theme.v1); return (
return ( <div>
<div> <Input
<Input type="text"
type="text" value={'Default Gauge Color'}
value={'Default Gauge Color'} disabled
disabled prefix={
prefix={ <div>
<div> <ColorPicker
<ColorPicker color={config.defaultColor}
color={config.defaultColor} onChange={(val) => onFieldChange('defaultColor', val)}
onChange={val => onFieldChange('defaultColor', val)} enableNamedColors={true}
enableNamedColors={true} />
</div>
}
/>
<Input
type="text"
value={'Arc Background'}
disabled
prefix={
<div>
<ColorPicker
color={config.arcBackground}
onChange={(val) => onFieldChange('arcBackground', val)}
enableNamedColors={true}
/>
</div>
}
/>
{config.thresholds.map((threshold, thresholdIdx) => {
return (
<HorizontalGroup>
<Input
type="text"
value={'Color'}
disabled
prefix={
<div>
<ColorPicker
color={threshold.color}
onChange={(newVal) => onThresholdFieldChange(thresholdIdx, 'color', newVal)}
enableNamedColors={true}
/>
</div>
}
/>
<InlineField label="Use metric">
<InlineSwitch
value={threshold.useMetric}
onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'useMetric', (evt.target as any).checked)}
/> />
</div> </InlineField>
} {threshold.useMetric ? (
/> <FieldNamePicker
<Input context={context}
type="text" value={threshold.metricName as string}
value={'Arc Background'} onChange={(newVal: any) => onThresholdFieldChange(thresholdIdx, 'metricName', newVal)}
disabled item={fieldNamePickerSettings}
prefix={
<div>
<ColorPicker
color={config.arcBackground}
onChange={val => onFieldChange('arcBackground', val) }
enableNamedColors={true}
/> />
</div> ) : (
} <Input
/> placeholder="value"
{ value={threshold.value}
config.thresholds.map((threshold, thresholdIdx) => { onChange={(evt) => onThresholdFieldChange(thresholdIdx, 'value', (evt.target as any).value)}
return ( />
<HorizontalGroup> )}
<Input <IconButton name="trash-alt" onClick={() => removeThreshold(thresholdIdx)}></IconButton>
type="text" </HorizontalGroup>
value={'Color'} );
disabled })}
prefix={ <Button variant="secondary" onClick={addThreshold}>
<div> Add Threshold
<ColorPicker </Button>
color={threshold.color} </div>
onChange={newVal => onThresholdFieldChange(thresholdIdx, 'color', newVal)} );
enableNamedColors={true} }}
/>
</div>
}
/>
<InlineField label="Use metric">
<InlineSwitch
value={threshold.useMetric}
onChange={evt => onThresholdFieldChange(thresholdIdx, 'useMetric', (evt.target as any).checked)}
/>
</InlineField>
{
threshold.useMetric ?
<FieldNamePicker
context={context}
value={threshold.metricName as string}
onChange={(newVal: any) => onThresholdFieldChange(thresholdIdx, 'metricName', newVal)}
item={fieldNamePickerSettings}
/> :
<Input
placeholder="value"
value={threshold.value}
onChange={evt => onThresholdFieldChange(thresholdIdx, 'value', (evt.target as any).value)}
/>
}
<IconButton name="trash-alt" onClick={() => removeThreshold(thresholdIdx)}></IconButton>
</HorizontalGroup>
)
})
}
<Button variant="secondary" onClick={addThreshold}>Add Threshold</Button>
</div>
)
}
}
</ThemeContext.Consumer> </ThemeContext.Consumer>
) );
} }

16
src/models/options.ts

@ -13,11 +13,15 @@ export class Options {
} }
private _setMin(): any { private _setMin(): any {
if(!this.grafanaOptions.gauge.min.metricName) { if (!this.grafanaOptions.gauge.min.metricName) {
this.minValue = this.grafanaOptions.gauge.min.value; this.minValue = this.grafanaOptions.gauge.min.value;
return; return;
} }
const filteredSeries = filterMetricListByAlias(this.grafanaSeriesList, this.grafanaOptions.gauge.min.metricName, 'Min'); const filteredSeries = filterMetricListByAlias(
this.grafanaSeriesList,
this.grafanaOptions.gauge.min.metricName,
'Min'
);
const serie = filteredSeries[0]; const serie = filteredSeries[0];
// Last value for now // Last value for now
const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST); const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST);
@ -25,11 +29,15 @@ export class Options {
} }
private _setMax(): any { private _setMax(): any {
if(!this.grafanaOptions.gauge.max.metricName) { if (!this.grafanaOptions.gauge.max.metricName) {
this.maxValue = this.grafanaOptions.gauge.max.value; this.maxValue = this.grafanaOptions.gauge.max.value;
return; return;
} }
const filteredSeries = filterMetricListByAlias(this.grafanaSeriesList, this.grafanaOptions.gauge.max.metricName, 'Max'); const filteredSeries = filterMetricListByAlias(
this.grafanaSeriesList,
this.grafanaOptions.gauge.max.metricName,
'Max'
);
const serie = filteredSeries[0]; const serie = filteredSeries[0];
// Last value for now // Last value for now
const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST); const aggregatedValue = getAggregatedValueFromSerie(serie, Aggregation.LAST);

4
src/models/series.ts

@ -1,18 +1,16 @@
import { ValueOptions } from 'types'; import { ValueOptions } from 'types';
import { filterMetricListByAlias } from '../utils'; import { filterMetricListByAlias } from '../utils';
import * as _ from 'lodash'; import * as _ from 'lodash';
// Convert Grafana series into Chartwerk series // Convert Grafana series into Chartwerk series
export class Series { export class Series {
private _seriesList; private _seriesList;
private _selectedSerieName; private _selectedSerieName;
constructor(grafanaSeriesList: any, private gaugeValueOptions: ValueOptions) { constructor(grafanaSeriesList: any, private gaugeValueOptions: ValueOptions) {
if(_.isEmpty(this.gaugeValueOptions.metricName)) { if (_.isEmpty(this.gaugeValueOptions.metricName)) {
throw new Error(`Value: metric is not selected. [See options: Extremum -> Value]`); throw new Error(`Value: metric is not selected. [See options: Extremum -> Value]`);
} }
this._selectedSerieName = this.gaugeValueOptions.metricName; this._selectedSerieName = this.gaugeValueOptions.metricName;

7
src/module.ts

@ -6,7 +6,6 @@ import { ThresholdsEditor } from './components/editors/ThresholdsEditor';
import { PanelPlugin } from '@grafana/data'; import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((builder) => { export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((builder) => {
return builder return builder
.addRadio({ .addRadio({
@ -39,7 +38,7 @@ export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((buil
name: 'Value', name: 'Value',
path: 'gauge.value.metricName', path: 'gauge.value.metricName',
category: ['Extremum'], category: ['Extremum'],
showIf: (config) => config.visualizationType === Pod.GAUGE showIf: (config) => config.visualizationType === Pod.GAUGE,
}) })
.addNumberInput({ .addNumberInput({
@ -106,9 +105,9 @@ export const plugin = new PanelPlugin<PanelOptions>(Panel).setPanelOptions((buil
defaultValue: { defaultValue: {
defaultColor: '#37872d', defaultColor: '#37872d',
arcBackground: 'rgba(38, 38, 38, 0.1)', arcBackground: 'rgba(38, 38, 38, 0.1)',
thresholds: [] thresholds: [],
}, },
showIf: (config) => config.visualizationType === Pod.GAUGE, showIf: (config) => config.visualizationType === Pod.GAUGE,
editor: ThresholdsEditor as any, editor: ThresholdsEditor as any,
}) });
}); });

10
src/types.ts

@ -12,16 +12,16 @@ export type ExtremumOptions = {
useMetric: false; useMetric: false;
value?: number; value?: number;
metricName?: string; metricName?: string;
} };
export type ValueOptions = { export type ValueOptions = {
metricName?: string metricName?: string;
} };
export enum IconPosition { export enum IconPosition {
UPPER_LEFT = 'Upper left', UPPER_LEFT = 'Upper left',
MIDDLE = 'Middle', MIDDLE = 'Middle',
UPPER_RIGHT = 'Upper right' UPPER_RIGHT = 'Upper right',
} }
export enum Pod { export enum Pod {
@ -33,5 +33,5 @@ export enum Pod {
export enum Aggregation { export enum Aggregation {
MIN = 'min', MIN = 'min',
MAX = 'max', MAX = 'max',
LAST = 'last' LAST = 'last',
} }

16
src/utils.ts

@ -3,11 +3,11 @@ import { Aggregation } from './types';
import * as _ from 'lodash'; import * as _ from 'lodash';
export function filterMetricListByAlias(list: any[], alias: string | undefined, option: string): any[] { export function filterMetricListByAlias(list: any[], alias: string | undefined, option: string): any[] {
const filteredSeries = _.filter(list, serie => serie.alias === alias); const filteredSeries = _.filter(list, (serie) => serie.alias === alias);
if(filteredSeries.length === 0) { if (filteredSeries.length === 0) {
throw new Error(`${option}: Can't find metric for ${alias} name.`); throw new Error(`${option}: Can't find metric for ${alias} name.`);
} }
if(filteredSeries.length > 1) { if (filteredSeries.length > 1) {
throw new Error(`${option}: Get ${filteredSeries.length} metrics for ${alias} name. Please choose one.`); throw new Error(`${option}: Get ${filteredSeries.length} metrics for ${alias} name. Please choose one.`);
} }
return filteredSeries; return filteredSeries;
@ -15,19 +15,19 @@ export function filterMetricListByAlias(list: any[], alias: string | undefined,
export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST): number | null { export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST): number | null {
// series types { datapoints: [number, number][]} // series types { datapoints: [number, number][]}
if(serie === undefined) { if (serie === undefined) {
return null; return null;
} }
if(serie.datapoints.length === 0) { if (serie.datapoints.length === 0) {
return null; return null;
} }
switch(aggregation) { switch (aggregation) {
case Aggregation.LAST: case Aggregation.LAST:
const lastRow = _.last((serie.datapoints as [number, number][])); const lastRow = _.last(serie.datapoints as [number, number][]);
// [0] because it is Grafan series. So 0 idx for values, 1 idx for timestamps // [0] because it is Grafan series. So 0 idx for values, 1 idx for timestamps
// @ts-ignore // @ts-ignore
return !_.isEmpty(lastRow) ? lastRow[0] : null; return !_.isEmpty(lastRow) ? lastRow[0] : null;
default: default:
throw new Error(`Unknown aggregation type: ${aggregation}`) throw new Error(`Unknown aggregation type: ${aggregation}`);
} }
} }

Loading…
Cancel
Save