Browse Source

linting

merge-requests/6/merge
rozetko 2 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
window.requestAnimationFrame(() => {
// TODO: switch / case pod type
const pod = new ChartwerkGaugePod(
(chartContainer as any).current,
series,
chartwerkOptions
);
const pod = new ChartwerkGaugePod((chartContainer as any).current, series, chartwerkOptions);
pod.render();
});
return (

163
src/components/editors/IconsEditor.tsx

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

188
src/components/editors/ThresholdsEditor.tsx

@ -1,17 +1,25 @@
import { FieldNamePicker } from '../../grafana/MatchersUI/FieldNamePicker';
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 * as _ from 'lodash';
interface Props {
defaultColor: string,
arcBackground: string,
thresholds: Array<ThresholdConfig>
defaultColor: string;
arcBackground: string;
thresholds: Array<ThresholdConfig>;
}
type ThresholdConfig = {
@ -19,13 +27,13 @@ type ThresholdConfig = {
useMetric: boolean;
value?: number;
metricName?: string;
}
};
const DEFAULT_THRESHOLD: ThresholdConfig = {
color: '#378372d',
useMetric: false,
value: 0
}
value: 0,
};
const fieldNamePickerSettings = {
settings: { width: 24 },
@ -37,17 +45,17 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
const addThreshold = () => {
config.thresholds.push(_.cloneDeep(DEFAULT_THRESHOLD));
onChange(config);
}
};
const removeThreshold = (idx: number) => {
config.thresholds.splice(idx, 1);
onChange(config);
}
};
const onFieldChange = (field: keyof Props, value: any) => {
config[field] = value;
onChange(config);
}
};
const onThresholdFieldChange = (thresholdIdx: number, field: keyof ThresholdConfig, value: any) => {
// @ts-ignore
@ -57,87 +65,85 @@ export function ThresholdsEditor({ onChange, value, context }: StandardEditorPro
return (
<ThemeContext.Consumer>
{
() => {
// const styles = getStyles(theme.v1);
return (
<div>
<Input
type="text"
value={'Default Gauge Color'}
disabled
prefix={
<div>
<ColorPicker
color={config.defaultColor}
onChange={val => onFieldChange('defaultColor', val)}
enableNamedColors={true}
{() => {
// const styles = getStyles(theme.v1);
return (
<div>
<Input
type="text"
value={'Default Gauge Color'}
disabled
prefix={
<div>
<ColorPicker
color={config.defaultColor}
onChange={(val) => onFieldChange('defaultColor', val)}
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>
}
/>
<Input
type="text"
value={'Arc Background'}
disabled
prefix={
<div>
<ColorPicker
color={config.arcBackground}
onChange={val => onFieldChange('arcBackground', val) }
enableNamedColors={true}
</InlineField>
{threshold.useMetric ? (
<FieldNamePicker
context={context}
value={threshold.metricName as string}
onChange={(newVal: any) => onThresholdFieldChange(thresholdIdx, 'metricName', newVal)}
item={fieldNamePickerSettings}
/>
</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)}
/>
</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>
)
}
}
) : (
<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>
)
);
}

16
src/models/options.ts

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

4
src/models/series.ts

@ -1,18 +1,16 @@
import { ValueOptions } from 'types';
import { filterMetricListByAlias } from '../utils';
import * as _ from 'lodash';
// Convert Grafana series into Chartwerk series
export class Series {
private _seriesList;
private _selectedSerieName;
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]`);
}
this._selectedSerieName = this.gaugeValueOptions.metricName;

7
src/module.ts

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

10
src/types.ts

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

16
src/utils.ts

@ -3,11 +3,11 @@ 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) {
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) {
if (filteredSeries.length > 1) {
throw new Error(`${option}: Get ${filteredSeries.length} metrics for ${alias} name. Please choose one.`);
}
return filteredSeries;
@ -15,19 +15,19 @@ export function filterMetricListByAlias(list: any[], alias: string | undefined,
export function getAggregatedValueFromSerie(serie: any, aggregation = Aggregation.LAST): number | null {
// series types { datapoints: [number, number][]}
if(serie === undefined) {
if (serie === undefined) {
return null;
}
if(serie.datapoints.length === 0) {
if (serie.datapoints.length === 0) {
return null;
}
switch(aggregation) {
switch (aggregation) {
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
// @ts-ignore
return !_.isEmpty(lastRow) ? lastRow[0] : null;
default:
throw new Error(`Unknown aggregation type: ${aggregation}`)
throw new Error(`Unknown aggregation type: ${aggregation}`);
}
}

Loading…
Cancel
Save