import {Component} from 'react'; import styled from '@emotion/styled'; import Feature from 'sentry/components/acl/feature'; import SelectControl from 'sentry/components/forms/controls/selectControl'; import Input from 'sentry/components/input'; import NumberDragControl from 'sentry/components/numberDragControl'; import {Tooltip} from 'sentry/components/tooltip'; import {t, tct, tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {ThresholdControlValue} from 'sentry/views/alerts/rules/metric/types'; import { AlertRuleComparisonType, AlertRuleThresholdType, } from 'sentry/views/alerts/rules/metric/types'; type Props = ThresholdControlValue & { comparisonType: AlertRuleComparisonType; disableThresholdType: boolean; disabled: boolean; onChange: (value: ThresholdControlValue, e: React.FormEvent) => void; onThresholdPeriodChange: (value: number) => void; onThresholdTypeChange: (thresholdType: AlertRuleThresholdType) => void; placeholder: string; thresholdPeriod: number | null; type: string; hideControl?: boolean; }; type State = { currentValue: string | null; }; class ThresholdControl extends Component { state: State = { currentValue: null, }; handleThresholdChange = (e: React.ChangeEvent) => { const {value} = e.target; // Only allow number and partial number inputs if (!/^[0-9]*\.?[0-9]*$/.test(value)) { return; } const {onChange, thresholdType} = this.props; // Empty input if (value === '') { this.setState({currentValue: null}); onChange({thresholdType, threshold: ''}, e); return; } // Only call onChange if the new number is valid, and not partially typed // (eg writing out the decimal '5.') if (/\.+0*$/.test(value)) { this.setState({currentValue: value}); return; } const numberValue = Number(value); this.setState({currentValue: null}); onChange({thresholdType, threshold: numberValue}, e); }; /** * Coerce the currentValue to a number and trigger the onChange. */ handleThresholdBlur = (e: React.FocusEvent) => { if (this.state.currentValue === null) { return; } const {onChange, thresholdType} = this.props; onChange({thresholdType, threshold: Number(this.state.currentValue)}, e); this.setState({currentValue: null}); }; handleTypeChange = ({value}) => { const {onThresholdTypeChange} = this.props; onThresholdTypeChange(value); }; handleDragChange = (delta: number, e: React.MouseEvent) => { const {onChange, thresholdType, threshold} = this.props; const currentValue = threshold || 0; onChange({thresholdType, threshold: currentValue + delta}, e); }; handleThresholdPeriodChange = ({value}) => { this.props.onThresholdPeriodChange(value); }; render() { const {currentValue} = this.state; const { thresholdPeriod, thresholdType, comparisonType, hideControl, threshold, placeholder, type, onChange: _, onThresholdTypeChange: __, disabled, disableThresholdType, } = this.props; return ( ({ ...provided, cursor: 'not-allowed', pointerEvents: 'auto', }), } : undefined } onChange={this.handleTypeChange} /> {!hideControl && ( , } )} > {comparisonType === AlertRuleComparisonType.CHANGE && ( % )} )} {!hideControl && ( ({ value, label: tn('For %s minute', 'For %s minutes', value), }))} onChange={this.handleThresholdPeriodChange} /> )} ); } } const Wrapper = styled('div')` display: flex; align-items: center; gap: ${space(1)}; `; const Container = styled('div')<{comparisonType: AlertRuleComparisonType}>` flex: 2; display: flex; align-items: center; flex-direction: ${p => p.comparisonType === AlertRuleComparisonType.COUNT ? 'row' : 'row-reverse'}; gap: ${space(1)}; `; const SelectContainer = styled('div')` flex: 1; `; const ThresholdContainer = styled('div')<{comparisonType: AlertRuleComparisonType}>` flex: 1; display: flex; flex-direction: row; align-items: center; `; const ThresholdInput = styled('div')` position: relative; display: flex; flex-direction: row; align-items: center; `; const PercentWrapper = styled('div')` margin-left: ${space(1)}; `; const DragContainer = styled('div')` position: absolute; top: 4px; right: 12px; `; export default ThresholdControl;