thresholdsStep.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import styled from '@emotion/styled';
  2. import CircleIndicator from 'sentry/components/circleIndicator';
  3. import FieldWrapper from 'sentry/components/forms/fieldGroup/fieldWrapper';
  4. import NumberField, {NumberFieldProps} from 'sentry/components/forms/fields/numberField';
  5. import SelectField, {SelectFieldProps} from 'sentry/components/forms/fields/selectField';
  6. import {t, tct} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import theme from 'sentry/utils/theme';
  9. import {getThresholdUnitSelectOptions} from 'sentry/views/dashboards/utils';
  10. import {BuildStep} from '../buildStep';
  11. type ThresholdErrors = {
  12. [K in ThresholdMaxKeys]?: string;
  13. };
  14. type ThresholdsStepProps = {
  15. errors: ThresholdErrors;
  16. onThresholdChange: (maxKey: ThresholdMaxKeys, value: string) => void;
  17. onUnitChange: (unit: string) => void;
  18. thresholdsConfig: ThresholdsConfig | null;
  19. dataType?: string;
  20. dataUnit?: string;
  21. };
  22. type ThresholdRowProp = {
  23. color: string;
  24. maxInputProps: NumberFieldProps;
  25. minInputProps: NumberFieldProps;
  26. unitOptions: {label: string; value: string}[];
  27. unitSelectProps: SelectFieldProps<any>;
  28. maxKey?: ThresholdMaxKeys;
  29. onThresholdChange?: (maxKey: ThresholdMaxKeys, value: string) => void;
  30. onUnitChange?: (maxKey: ThresholdMaxKeys, value: string) => void;
  31. };
  32. export enum ThresholdMaxKeys {
  33. MAX_1 = 'max1',
  34. MAX_2 = 'max2',
  35. }
  36. type ThresholdMaxValues = {
  37. [K in ThresholdMaxKeys]?: number;
  38. };
  39. export type ThresholdsConfig = {
  40. max_values: ThresholdMaxValues;
  41. unit: string | null;
  42. };
  43. const WIDGET_INDICATOR_SIZE = 15;
  44. function ThresholdRow({
  45. color,
  46. minInputProps,
  47. maxInputProps,
  48. onThresholdChange,
  49. onUnitChange,
  50. maxKey,
  51. unitOptions,
  52. unitSelectProps,
  53. }: ThresholdRowProp) {
  54. const handleChange = (val: string) => {
  55. if (onThresholdChange && maxKey) {
  56. onThresholdChange(maxKey, val);
  57. }
  58. };
  59. return (
  60. <ThresholdRowWrapper>
  61. <CircleIndicator color={color} size={WIDGET_INDICATOR_SIZE} />
  62. <StyledNumberField {...minInputProps} inline={false} disabled />
  63. {t('to')}
  64. <StyledNumberField onChange={handleChange} {...maxInputProps} inline={false} />
  65. {unitOptions.length > 0 && (
  66. <StyledSelectField
  67. {...unitSelectProps}
  68. onChange={onUnitChange}
  69. options={unitOptions}
  70. inline={false}
  71. />
  72. )}
  73. </ThresholdRowWrapper>
  74. );
  75. }
  76. function ThresholdsStep({
  77. thresholdsConfig,
  78. onThresholdChange,
  79. onUnitChange,
  80. errors,
  81. dataType = '',
  82. dataUnit = '',
  83. }: ThresholdsStepProps) {
  84. const maxOneValue = thresholdsConfig?.max_values[ThresholdMaxKeys.MAX_1] ?? '';
  85. const maxTwoValue = thresholdsConfig?.max_values[ThresholdMaxKeys.MAX_2] ?? '';
  86. const unit = thresholdsConfig?.unit ?? dataUnit;
  87. const unitOptions = ['duration', 'rate'].includes(dataType)
  88. ? getThresholdUnitSelectOptions(dataType)
  89. : [];
  90. const thresholdRowProps: ThresholdRowProp[] = [
  91. {
  92. maxKey: ThresholdMaxKeys.MAX_1,
  93. minInputProps: {
  94. name: 'firstMinimum',
  95. value: 0,
  96. 'aria-label': 'First Minimum',
  97. },
  98. maxInputProps: {
  99. name: 'firstMaximum',
  100. value: maxOneValue,
  101. 'aria-label': 'First Maximum',
  102. error: errors?.max1,
  103. },
  104. color: theme.green300,
  105. unitOptions,
  106. unitSelectProps: {
  107. name: 'First unit select',
  108. value: unit,
  109. },
  110. },
  111. {
  112. maxKey: ThresholdMaxKeys.MAX_2,
  113. minInputProps: {
  114. name: 'secondMinimum',
  115. value: maxOneValue,
  116. 'aria-label': 'Second Minimum',
  117. },
  118. maxInputProps: {
  119. name: 'secondMaximum',
  120. value: maxTwoValue,
  121. 'aria-label': 'Second Maximum',
  122. error: errors?.max2,
  123. },
  124. color: theme.yellow300,
  125. unitOptions,
  126. unitSelectProps: {
  127. name: 'Second unit select',
  128. value: unit,
  129. disabled: true,
  130. },
  131. },
  132. {
  133. minInputProps: {
  134. name: 'thirdMinimum',
  135. value: maxTwoValue,
  136. 'aria-label': 'Third Minimum',
  137. },
  138. maxInputProps: {
  139. name: 'thirdMaximum',
  140. disabled: true,
  141. placeholder: t('No max'),
  142. 'aria-label': 'Third Maximum',
  143. },
  144. color: theme.red300,
  145. unitOptions,
  146. unitSelectProps: {
  147. name: 'Third unit select',
  148. value: unit,
  149. disabled: true,
  150. },
  151. },
  152. ];
  153. return (
  154. <BuildStep
  155. title={t('Set thresholds')}
  156. description={tct(
  157. 'Set thresholds to identify problematic widgets. For example: setting the max values, [thresholdValues] will display a green indicator for results in the range [greenRange], a yellow indicator for results in the range [yellowRange] and a red indicator for results above [redValue].',
  158. {
  159. thresholdValues: <HighlightedText>(green: 100, yellow: 200)</HighlightedText>,
  160. greenRange: <HighlightedText>[0 - 100]</HighlightedText>,
  161. yellowRange: <HighlightedText>(100 - 200]</HighlightedText>,
  162. redValue: <HighlightedText>200</HighlightedText>,
  163. }
  164. )}
  165. >
  166. <ThresholdsContainer>
  167. {thresholdRowProps.map((props, index) => (
  168. <ThresholdRow
  169. {...props}
  170. onThresholdChange={onThresholdChange}
  171. onUnitChange={onUnitChange}
  172. key={index}
  173. />
  174. ))}
  175. </ThresholdsContainer>
  176. </BuildStep>
  177. );
  178. }
  179. const ThresholdRowWrapper = styled('div')`
  180. display: flex;
  181. align-items: center;
  182. gap: ${space(2)};
  183. `;
  184. const ThresholdsContainer = styled('div')`
  185. display: flex;
  186. flex-direction: column;
  187. gap: ${space(2)};
  188. margin-top: ${space(1)};
  189. ${FieldWrapper} {
  190. padding: 0;
  191. border-bottom: none;
  192. }
  193. `;
  194. const StyledNumberField = styled(NumberField)`
  195. width: 200px;
  196. `;
  197. const StyledSelectField = styled(SelectField)`
  198. min-width: 150px;
  199. `;
  200. const HighlightedText = styled('span')`
  201. font-family: ${p => p.theme.text.familyMono};
  202. color: ${p => p.theme.pink300};
  203. `;
  204. export default ThresholdsStep;