mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeries: Add option to disconnect values (#70097)
Co-authored-by: nmarrs <nathanielmarrs@gmail.com> Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
31
public/app/plugins/panel/timeseries/InsertNullsEditor.tsx
Normal file
31
public/app/plugins/panel/timeseries/InsertNullsEditor.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { HorizontalGroup, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { InputPrefix, NullsThresholdInput } from './NullsThresholdInput';
|
||||
|
||||
const DISCONNECT_OPTIONS: Array<SelectableValue<boolean | number>> = [
|
||||
{
|
||||
label: 'Never',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: 'Threshold',
|
||||
value: 3600000, // 1h
|
||||
},
|
||||
];
|
||||
|
||||
type Props = FieldOverrideEditorProps<boolean | number, unknown>;
|
||||
|
||||
export const InsertNullsEditor = ({ value, onChange }: Props) => {
|
||||
const isThreshold = typeof value === 'number';
|
||||
DISCONNECT_OPTIONS[1].value = isThreshold ? value : 3600000; // 1h
|
||||
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<RadioButtonGroup value={value} options={DISCONNECT_OPTIONS} onChange={onChange} />
|
||||
{isThreshold && <NullsThresholdInput value={value} onChange={onChange} inputPrefix={InputPrefix.GreaterThan} />}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
57
public/app/plugins/panel/timeseries/NullsThresholdInput.tsx
Normal file
57
public/app/plugins/panel/timeseries/NullsThresholdInput.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
|
||||
import { rangeUtil } from '@grafana/data';
|
||||
import { Input } from '@grafana/ui';
|
||||
|
||||
export enum InputPrefix {
|
||||
LessThan = 'lessthan',
|
||||
GreaterThan = 'greaterthan',
|
||||
}
|
||||
|
||||
type Props = { value: number; onChange: (value?: number | boolean | undefined) => void; inputPrefix?: InputPrefix };
|
||||
|
||||
export const NullsThresholdInput = ({ value, onChange, inputPrefix }: Props) => {
|
||||
const formattedTime = rangeUtil.secondsToHms(value / 1000);
|
||||
const checkAndUpdate = (txt: string) => {
|
||||
let val: boolean | number = false;
|
||||
if (txt) {
|
||||
try {
|
||||
val = rangeUtil.intervalToMs(txt);
|
||||
} catch (err) {
|
||||
console.warn('ERROR', err);
|
||||
}
|
||||
}
|
||||
onChange(val);
|
||||
};
|
||||
|
||||
const handleEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key !== 'Enter') {
|
||||
return;
|
||||
}
|
||||
checkAndUpdate(e.currentTarget.value);
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
checkAndUpdate(e.currentTarget.value);
|
||||
};
|
||||
|
||||
const prefix =
|
||||
inputPrefix === InputPrefix.GreaterThan ? (
|
||||
<div>></div>
|
||||
) : inputPrefix === InputPrefix.LessThan ? (
|
||||
<div><</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Input
|
||||
autoFocus={false}
|
||||
placeholder="never"
|
||||
width={10}
|
||||
defaultValue={formattedTime}
|
||||
onKeyDown={handleEnterKey}
|
||||
onBlur={handleBlur}
|
||||
prefix={prefix}
|
||||
spellCheck={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FieldOverrideEditorProps, rangeUtil, SelectableValue } from '@grafana/data';
|
||||
import { HorizontalGroup, Input, RadioButtonGroup } from '@grafana/ui';
|
||||
import { FieldOverrideEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { HorizontalGroup, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { InputPrefix, NullsThresholdInput } from './NullsThresholdInput';
|
||||
|
||||
const GAPS_OPTIONS: Array<SelectableValue<boolean | number>> = [
|
||||
{
|
||||
@@ -22,47 +24,12 @@ type Props = FieldOverrideEditorProps<boolean | number, unknown>;
|
||||
|
||||
export const SpanNullsEditor = ({ value, onChange }: Props) => {
|
||||
const isThreshold = typeof value === 'number';
|
||||
const formattedTime = isThreshold ? rangeUtil.secondsToHms(value / 1000) : undefined;
|
||||
GAPS_OPTIONS[2].value = isThreshold ? value : 3600000; // 1h
|
||||
|
||||
const checkAndUpdate = (txt: string) => {
|
||||
let val: boolean | number = false;
|
||||
if (txt) {
|
||||
try {
|
||||
val = rangeUtil.intervalToSeconds(txt) * 1000;
|
||||
} catch (err) {
|
||||
console.warn('ERROR', err);
|
||||
}
|
||||
}
|
||||
onChange(val);
|
||||
};
|
||||
|
||||
const handleEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key !== 'Enter') {
|
||||
return;
|
||||
}
|
||||
checkAndUpdate(e.currentTarget.value);
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
checkAndUpdate(e.currentTarget.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<RadioButtonGroup value={value} options={GAPS_OPTIONS} onChange={onChange} />
|
||||
{isThreshold && (
|
||||
<Input
|
||||
autoFocus={false}
|
||||
placeholder="never"
|
||||
width={10}
|
||||
defaultValue={formattedTime}
|
||||
onKeyDown={handleEnterKey}
|
||||
onBlur={handleBlur}
|
||||
prefix={<div><</div>}
|
||||
spellCheck={false}
|
||||
/>
|
||||
)}
|
||||
{isThreshold && <NullsThresholdInput value={value} onChange={onChange} inputPrefix={InputPrefix.LessThan} />}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from '@grafana/schema';
|
||||
import { graphFieldOptions, commonOptionsBuilder } from '@grafana/ui';
|
||||
|
||||
import { InsertNullsEditor } from './InsertNullsEditor';
|
||||
import { LineStyleEditor } from './LineStyleEditor';
|
||||
import { SpanNullsEditor } from './SpanNullsEditor';
|
||||
import { ThresholdsStyleEditor } from './ThresholdsStyleEditor';
|
||||
@@ -74,7 +75,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
settings: {
|
||||
options: graphFieldOptions.lineInterpolation,
|
||||
},
|
||||
showIf: (c) => c.drawStyle === GraphDrawStyle.Line,
|
||||
showIf: (config) => config.drawStyle === GraphDrawStyle.Line,
|
||||
})
|
||||
.addRadio({
|
||||
path: 'barAlignment',
|
||||
@@ -84,7 +85,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
settings: {
|
||||
options: graphFieldOptions.barAlignment,
|
||||
},
|
||||
showIf: (c) => c.drawStyle === GraphDrawStyle.Bars,
|
||||
showIf: (config) => config.drawStyle === GraphDrawStyle.Bars,
|
||||
})
|
||||
.addSliderInput({
|
||||
path: 'lineWidth',
|
||||
@@ -97,7 +98,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
step: 1,
|
||||
ariaLabelForHandle: 'Line width',
|
||||
},
|
||||
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
|
||||
showIf: (config) => config.drawStyle !== GraphDrawStyle.Points,
|
||||
})
|
||||
.addSliderInput({
|
||||
path: 'fillOpacity',
|
||||
@@ -110,7 +111,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
step: 1,
|
||||
ariaLabelForHandle: 'Fill opacity',
|
||||
},
|
||||
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
|
||||
showIf: (config) => config.drawStyle !== GraphDrawStyle.Points,
|
||||
})
|
||||
.addRadio({
|
||||
path: 'gradientMode',
|
||||
@@ -120,7 +121,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
settings: {
|
||||
options: graphFieldOptions.fillGradient,
|
||||
},
|
||||
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
|
||||
showIf: (config) => config.drawStyle !== GraphDrawStyle.Points,
|
||||
})
|
||||
.addFieldNamePicker({
|
||||
path: 'fillBelowTo',
|
||||
@@ -136,11 +137,11 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
path: 'lineStyle',
|
||||
name: 'Line style',
|
||||
category: categoryStyles,
|
||||
showIf: (c) => c.drawStyle === GraphDrawStyle.Line,
|
||||
showIf: (config) => config.drawStyle === GraphDrawStyle.Line,
|
||||
editor: LineStyleEditor,
|
||||
override: LineStyleEditor,
|
||||
process: identityOverrideProcessor,
|
||||
shouldApply: (f) => f.type === FieldType.number,
|
||||
shouldApply: (field) => field.type === FieldType.number,
|
||||
})
|
||||
.addCustomEditor<void, boolean>({
|
||||
id: 'spanNulls',
|
||||
@@ -150,8 +151,20 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
defaultValue: false,
|
||||
editor: SpanNullsEditor,
|
||||
override: SpanNullsEditor,
|
||||
showIf: (c) => c.drawStyle === GraphDrawStyle.Line,
|
||||
shouldApply: (f) => f.type !== FieldType.time,
|
||||
showIf: (config) => config.drawStyle === GraphDrawStyle.Line,
|
||||
shouldApply: (field) => field.type !== FieldType.time,
|
||||
process: identityOverrideProcessor,
|
||||
})
|
||||
.addCustomEditor<void, boolean>({
|
||||
id: 'insertNulls',
|
||||
path: 'insertNulls',
|
||||
name: 'Disconnect values',
|
||||
category: categoryStyles,
|
||||
defaultValue: false,
|
||||
editor: InsertNullsEditor,
|
||||
override: InsertNullsEditor,
|
||||
showIf: (config) => config.drawStyle === GraphDrawStyle.Line,
|
||||
shouldApply: (field) => field.type !== FieldType.time,
|
||||
process: identityOverrideProcessor,
|
||||
})
|
||||
.addRadio({
|
||||
@@ -162,7 +175,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
settings: {
|
||||
options: graphFieldOptions.showPoints,
|
||||
},
|
||||
showIf: (c) => c.drawStyle !== GraphDrawStyle.Points,
|
||||
showIf: (config) => config.drawStyle !== GraphDrawStyle.Points,
|
||||
})
|
||||
.addSliderInput({
|
||||
path: 'pointSize',
|
||||
@@ -175,7 +188,7 @@ export function getGraphFieldConfig(cfg: GraphFieldConfig): SetFieldConfigOption
|
||||
step: 1,
|
||||
ariaLabelForHandle: 'Point size',
|
||||
},
|
||||
showIf: (c) => c.showPoints !== VisibilityMode.Never || c.drawStyle === GraphDrawStyle.Points,
|
||||
showIf: (config) => config.showPoints !== VisibilityMode.Never || config.drawStyle === GraphDrawStyle.Points,
|
||||
});
|
||||
|
||||
commonOptionsBuilder.addStackingConfig(builder, cfg.stacking, categoryStyles);
|
||||
|
||||
Reference in New Issue
Block a user