mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeSeriesPanel: Allow threshold indicators without change handler (#60575)
This commit is contained in:
@@ -45,6 +45,13 @@ export interface PanelContext {
|
||||
*/
|
||||
canEditThresholds?: boolean;
|
||||
|
||||
/**
|
||||
* Shows threshold indicators on the right-hand side of the panel
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
showThresholds?: boolean;
|
||||
|
||||
/**
|
||||
* Called when a panel wants to change default thresholds configuration
|
||||
*
|
||||
|
||||
@@ -59,43 +59,6 @@ export class QueryRows extends PureComponent<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
onChangeThreshold = (thresholds: ThresholdsConfig, index: number) => {
|
||||
const { queries, onQueriesChange } = this.props;
|
||||
|
||||
const referencedRefId = queries[index].refId;
|
||||
|
||||
onQueriesChange(
|
||||
queries.map((query) => {
|
||||
if (!isExpressionQuery(query.model)) {
|
||||
return query;
|
||||
}
|
||||
|
||||
if (query.model.conditions && query.model.conditions[0].query.params[0] === referencedRefId) {
|
||||
return {
|
||||
...query,
|
||||
model: {
|
||||
...query.model,
|
||||
conditions: query.model.conditions.map((condition, conditionIndex) => {
|
||||
// Only update the first condition for a given refId.
|
||||
if (condition.query.params[0] === referencedRefId && conditionIndex === 0) {
|
||||
return {
|
||||
...condition,
|
||||
evaluator: {
|
||||
...condition.evaluator,
|
||||
params: [parseFloat(thresholds.steps[1].value.toPrecision(3))],
|
||||
},
|
||||
};
|
||||
}
|
||||
return condition;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return query;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
onChangeDataSource = (settings: DataSourceInstanceSettings, index: number) => {
|
||||
const { queries, onQueriesChange } = this.props;
|
||||
|
||||
@@ -253,7 +216,6 @@ export class QueryRows extends PureComponent<Props> {
|
||||
onDuplicateQuery={this.props.onDuplicateQuery}
|
||||
onChangeTimeRange={this.onChangeTimeRange}
|
||||
thresholds={thresholdByRefId[query.refId]}
|
||||
onChangeThreshold={this.onChangeThreshold}
|
||||
onRunQueries={this.props.onRunQueries}
|
||||
condition={this.props.condition}
|
||||
onSetCondition={this.props.onSetCondition}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { cloneDeep, noop } from 'lodash';
|
||||
import React, { FC, useState } from 'react';
|
||||
|
||||
import {
|
||||
@@ -39,7 +39,7 @@ interface Props {
|
||||
onRunQueries: () => void;
|
||||
index: number;
|
||||
thresholds: ThresholdsConfig;
|
||||
onChangeThreshold: (thresholds: ThresholdsConfig, index: number) => void;
|
||||
onChangeThreshold?: (thresholds: ThresholdsConfig, index: number) => void;
|
||||
condition: string | null;
|
||||
onSetCondition: (refId: string) => void;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export const QueryWrapper: FC<Props> = ({
|
||||
changePanel={changePluginId}
|
||||
currentPanel={pluginId}
|
||||
thresholds={thresholds}
|
||||
onThresholdsChange={(thresholds) => onChangeThreshold(thresholds, index)}
|
||||
onThresholdsChange={onChangeThreshold ? (thresholds) => onChangeThreshold(thresholds, index) : noop}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ export const VizWrapper: FC<Props> = ({ data, currentPanel, changePanel, onThres
|
||||
const context: PanelContext = useMemo(
|
||||
() => ({
|
||||
eventBus: appEvents,
|
||||
canEditThresholds: true,
|
||||
canEditThresholds: false,
|
||||
showThresholds: true,
|
||||
onThresholdsChange: onThresholdsChange,
|
||||
}),
|
||||
[onThresholdsChange]
|
||||
|
||||
@@ -38,7 +38,8 @@ export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
|
||||
onChangeTimeRange,
|
||||
replaceVariables,
|
||||
}) => {
|
||||
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, onSplitOpen } = usePanelContext();
|
||||
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, showThresholds, onSplitOpen } =
|
||||
usePanelContext();
|
||||
|
||||
const getFieldLinks = (field: Field, rowIndex: number) => {
|
||||
return getFieldLinksForExplore({ field, rowIndex, splitOpenFn: onSplitOpen, range: timeRange });
|
||||
@@ -320,11 +321,11 @@ export const CandlestickPanel: React.FC<CandlestickPanelProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{canEditThresholds && onThresholdsChange && (
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
|
||||
<ThresholdControlsPlugin
|
||||
config={config}
|
||||
fieldConfig={fieldConfig}
|
||||
onThresholdsChange={onThresholdsChange}
|
||||
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
replaceVariables,
|
||||
id,
|
||||
}) => {
|
||||
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, onSplitOpen } = usePanelContext();
|
||||
const { sync, canAddAnnotations, onThresholdsChange, canEditThresholds, showThresholds, onSplitOpen } =
|
||||
usePanelContext();
|
||||
|
||||
const getFieldLinks = (field: Field, rowIndex: number) => {
|
||||
return getFieldLinksForExplore({ field, rowIndex, splitOpenFn: onSplitOpen, range: timeRange });
|
||||
@@ -141,11 +142,11 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{canEditThresholds && onThresholdsChange && (
|
||||
{((canEditThresholds && onThresholdsChange) || showThresholds) && (
|
||||
<ThresholdControlsPlugin
|
||||
config={config}
|
||||
fieldConfig={fieldConfig}
|
||||
onThresholdsChange={onThresholdsChange}
|
||||
onThresholdsChange={canEditThresholds ? onThresholdsChange : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const GUTTER_SIZE = 60;
|
||||
interface ThresholdControlsPluginProps {
|
||||
config: UPlotConfigBuilder;
|
||||
fieldConfig: FieldConfigSource;
|
||||
onThresholdsChange: (thresholds: ThresholdsConfig) => void;
|
||||
onThresholdsChange?: (thresholds: ThresholdsConfig) => void;
|
||||
}
|
||||
|
||||
export const ThresholdControlsPlugin: React.FC<ThresholdControlsPluginProps> = ({
|
||||
@@ -59,15 +59,10 @@ export const ThresholdControlsPlugin: React.FC<ThresholdControlsPluginProps> = (
|
||||
|
||||
const height = plot.bbox.height / window.devicePixelRatio;
|
||||
|
||||
const handle = (
|
||||
<ThresholdDragHandle
|
||||
key={`${step.value}-${i}`}
|
||||
step={step}
|
||||
y={yPos}
|
||||
dragBounds={{ top: 0, bottom: height }}
|
||||
mapPositionToValue={(y) => plot.posToVal(y, scale)}
|
||||
formatValue={(v) => getValueFormat(scale)(v, decimals).text}
|
||||
onChange={(value) => {
|
||||
const isEditable = typeof onThresholdsChange === 'function';
|
||||
|
||||
const onChange = isEditable
|
||||
? (value: number) => {
|
||||
const nextSteps = [
|
||||
...thresholds.steps.slice(0, i),
|
||||
...thresholds.steps.slice(i + 1),
|
||||
@@ -78,7 +73,18 @@ export const ThresholdControlsPlugin: React.FC<ThresholdControlsPluginProps> = (
|
||||
...thresholds,
|
||||
steps: nextSteps,
|
||||
});
|
||||
}}
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const handle = (
|
||||
<ThresholdDragHandle
|
||||
key={`${step.value}-${i}`}
|
||||
step={step}
|
||||
y={yPos}
|
||||
dragBounds={{ top: 0, bottom: height }}
|
||||
mapPositionToValue={(y) => plot.posToVal(y, scale)}
|
||||
formatValue={(v) => getValueFormat(scale)(v, decimals).text}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { noop } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import Draggable, { DraggableBounds } from 'react-draggable';
|
||||
|
||||
@@ -12,7 +13,7 @@ interface ThresholdDragHandleProps {
|
||||
y: number;
|
||||
dragBounds: DraggableBounds;
|
||||
mapPositionToValue: (y: number) => number;
|
||||
onChange: (value: number) => void;
|
||||
onChange?: (value: number) => void;
|
||||
formatValue: (value: number) => string;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,8 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
yPos = dragBounds.top ?? y;
|
||||
}
|
||||
|
||||
const styles = useStyles2((theme) => getStyles(theme, step, outOfBounds));
|
||||
const disabled = typeof onChange !== 'function';
|
||||
const styles = useStyles2((theme) => getStyles(theme, step, outOfBounds, disabled));
|
||||
const [currentValue, setCurrentValue] = useState(step.value);
|
||||
|
||||
const textColor = useMemo(() => {
|
||||
@@ -57,11 +59,16 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
<Draggable
|
||||
axis="y"
|
||||
grid={[1, 1]}
|
||||
onStop={(_e, d) => {
|
||||
onChange(mapPositionToValue(d.lastY));
|
||||
// as of https://github.com/react-grid-layout/react-draggable/issues/390#issuecomment-623237835
|
||||
return false;
|
||||
}}
|
||||
disabled={disabled}
|
||||
onStop={
|
||||
disabled
|
||||
? noop
|
||||
: (_e, d) => {
|
||||
onChange(mapPositionToValue(d.lastY));
|
||||
// as of https://github.com/react-grid-layout/react-draggable/issues/390#issuecomment-623237835
|
||||
return false;
|
||||
}
|
||||
}
|
||||
onDrag={(_e, d) => setCurrentValue(mapPositionToValue(d.lastY))}
|
||||
position={{ x: 0, y: yPos }}
|
||||
bounds={dragBounds}
|
||||
@@ -75,7 +82,7 @@ export const ThresholdDragHandle: React.FC<ThresholdDragHandleProps> = ({
|
||||
|
||||
ThresholdDragHandle.displayName = 'ThresholdDragHandle';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, step: Threshold, outOfBounds: OutOfBounds) => {
|
||||
const getStyles = (theme: GrafanaTheme2, step: Threshold, outOfBounds: OutOfBounds, disabled?: boolean) => {
|
||||
const mainColor = theme.visualization.getColorByName(step.color);
|
||||
const arrowStyles = getArrowStyles(outOfBounds);
|
||||
const isOutOfBounds = outOfBounds !== 'none';
|
||||
@@ -90,7 +97,7 @@ const getStyles = (theme: GrafanaTheme2, step: Threshold, outOfBounds: OutOfBoun
|
||||
height: 18px;
|
||||
margin-top: -9px;
|
||||
border-color: ${mainColor};
|
||||
cursor: grab;
|
||||
cursor: ${disabled ? 'initial' : 'grab'};
|
||||
border-top-right-radius: ${theme.shape.borderRadius(1)};
|
||||
border-bottom-right-radius: ${theme.shape.borderRadius(1)};
|
||||
${isOutOfBounds &&
|
||||
|
||||
Reference in New Issue
Block a user