thresholdControl.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import {Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import {NumberDragInput} from 'sentry/components/core/input/numberDragInput';
  4. import {Select} from 'sentry/components/core/select';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import type {ThresholdControlValue} from 'sentry/views/alerts/rules/metric/types';
  8. import {
  9. AlertRuleComparisonType,
  10. AlertRuleThresholdType,
  11. } from 'sentry/views/alerts/rules/metric/types';
  12. interface Props extends ThresholdControlValue {
  13. comparisonType: AlertRuleComparisonType;
  14. disableThresholdType: boolean;
  15. disabled: boolean;
  16. onChange: (value: ThresholdControlValue, e: React.FormEvent) => void;
  17. onThresholdTypeChange: (thresholdType: AlertRuleThresholdType) => void;
  18. placeholder: string;
  19. type: string;
  20. hideControl?: boolean;
  21. }
  22. type State = {
  23. currentValue: string | null;
  24. };
  25. class ThresholdControl extends Component<Props, State> {
  26. state: State = {
  27. currentValue: null,
  28. };
  29. handleThresholdChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  30. // Only allow number and partial number inputs
  31. if (!/^[0-9]*\.?[0-9]*$/.test(event.target.value)) {
  32. return;
  33. }
  34. // Empty input
  35. if (event.target.value === '') {
  36. this.setState({currentValue: null});
  37. this.props.onChange(
  38. {thresholdType: this.props.thresholdType, threshold: ''},
  39. event
  40. );
  41. return;
  42. }
  43. // Only call onChange if the new number is valid, and not partially typed
  44. // (eg writing out the decimal '5.')
  45. if (/\.+0*$/.test(event.target.value)) {
  46. this.setState({currentValue: event.target.value});
  47. return;
  48. }
  49. const numberValue = Number(event.target.value);
  50. this.setState({currentValue: null});
  51. this.props.onChange(
  52. {thresholdType: this.props.thresholdType, threshold: numberValue},
  53. event
  54. );
  55. };
  56. handleTypeChange = ({value}: any) => {
  57. this.props.onThresholdTypeChange(value);
  58. };
  59. render() {
  60. const {currentValue} = this.state;
  61. const {
  62. thresholdType,
  63. comparisonType,
  64. hideControl,
  65. threshold,
  66. placeholder,
  67. type,
  68. onChange: _,
  69. onThresholdTypeChange: __,
  70. disabled,
  71. disableThresholdType,
  72. } = this.props;
  73. const inputValue = currentValue ?? threshold ?? '';
  74. return (
  75. <Wrapper>
  76. <Container comparisonType={comparisonType}>
  77. <SelectContainer>
  78. <Select
  79. isDisabled={disabled || disableThresholdType}
  80. name={`${type}ThresholdType`}
  81. value={thresholdType}
  82. options={[
  83. {
  84. value: AlertRuleThresholdType.BELOW,
  85. label:
  86. comparisonType === AlertRuleComparisonType.COUNT
  87. ? hideControl
  88. ? t('When below Critical or Warning')
  89. : t('Below')
  90. : hideControl
  91. ? t('When lower than Critical or Warning')
  92. : t('Lower than'),
  93. },
  94. {
  95. value: AlertRuleThresholdType.ABOVE,
  96. label:
  97. comparisonType === AlertRuleComparisonType.COUNT
  98. ? hideControl
  99. ? t('When above Critical or Warning')
  100. : t('Above')
  101. : hideControl
  102. ? t('When higher than Critical or Warning')
  103. : t('Higher than'),
  104. },
  105. ]}
  106. components={disableThresholdType ? {DropdownIndicator: null} : undefined}
  107. styles={
  108. disableThresholdType
  109. ? {
  110. control: (provided: any) => ({
  111. ...provided,
  112. cursor: 'not-allowed',
  113. pointerEvents: 'auto',
  114. }),
  115. }
  116. : undefined
  117. }
  118. onChange={this.handleTypeChange}
  119. />
  120. </SelectContainer>
  121. {!hideControl && (
  122. <ThresholdContainer comparisonType={comparisonType}>
  123. <ThresholdInput>
  124. <NumberDragInput
  125. min={0}
  126. size="md"
  127. axis="y"
  128. name={`${type}Threshold`}
  129. data-test-id={`${type}-threshold`}
  130. value={inputValue}
  131. // When shift key is held down, the pointer delta is multiplied by 1, making
  132. // the threshold change more granular and precise than the step size.
  133. shiftKeyMultiplier={1}
  134. disabled={disabled}
  135. placeholder={placeholder}
  136. onChange={this.handleThresholdChange}
  137. // Disable lastpass autocomplete
  138. data-lpignore="true"
  139. />
  140. </ThresholdInput>
  141. {comparisonType === AlertRuleComparisonType.CHANGE && (
  142. <PercentWrapper>%</PercentWrapper>
  143. )}
  144. </ThresholdContainer>
  145. )}
  146. </Container>
  147. </Wrapper>
  148. );
  149. }
  150. }
  151. const Wrapper = styled('div')`
  152. display: flex;
  153. align-items: center;
  154. gap: ${space(1)};
  155. `;
  156. const Container = styled('div')<{comparisonType: AlertRuleComparisonType}>`
  157. flex: 2;
  158. display: flex;
  159. align-items: center;
  160. flex-direction: ${p =>
  161. p.comparisonType === AlertRuleComparisonType.COUNT ? 'row' : 'row-reverse'};
  162. gap: ${space(1)};
  163. `;
  164. const SelectContainer = styled('div')`
  165. flex: 1;
  166. `;
  167. const ThresholdContainer = styled('div')<{comparisonType: AlertRuleComparisonType}>`
  168. flex: 1;
  169. display: flex;
  170. flex-direction: row;
  171. align-items: center;
  172. `;
  173. const ThresholdInput = styled('div')`
  174. position: relative;
  175. display: flex;
  176. flex-direction: row;
  177. align-items: center;
  178. `;
  179. const PercentWrapper = styled('div')`
  180. margin-left: ${space(1)};
  181. `;
  182. export default ThresholdControl;