wizardField.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import SelectControl from 'sentry/components/forms/controls/selectControl';
  4. import FormField, {FormFieldProps} from 'sentry/components/forms/formField';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import {Organization} from 'sentry/types';
  8. import {explodeFieldString, generateFieldAsString} from 'sentry/utils/discover/fields';
  9. import {Dataset} from 'sentry/views/alerts/rules/metric/types';
  10. import {
  11. AlertType,
  12. AlertWizardAlertNames,
  13. AlertWizardRuleTemplates,
  14. } from 'sentry/views/alerts/wizard/options';
  15. import {QueryField} from 'sentry/views/discover/table/queryField';
  16. import {FieldValueKind} from 'sentry/views/discover/table/types';
  17. import {generateFieldOptions} from 'sentry/views/discover/utils';
  18. import {getFieldOptionConfig} from './metricField';
  19. type MenuOption = {label: string; value: AlertType};
  20. type GroupedMenuOption = {label: string; options: Array<MenuOption>};
  21. type Props = Omit<FormFieldProps, 'children'> & {
  22. organization: Organization;
  23. alertType?: AlertType;
  24. /**
  25. * Optionally set a width for each column of selector
  26. */
  27. columnWidth?: number;
  28. inFieldLabels?: boolean;
  29. };
  30. export default function WizardField({
  31. organization,
  32. columnWidth,
  33. inFieldLabels,
  34. alertType,
  35. ...fieldProps
  36. }: Props) {
  37. const menuOptions: GroupedMenuOption[] = [
  38. {
  39. label: t('ERRORS'),
  40. options: [
  41. {
  42. label: AlertWizardAlertNames.num_errors,
  43. value: 'num_errors',
  44. },
  45. {
  46. label: AlertWizardAlertNames.users_experiencing_errors,
  47. value: 'users_experiencing_errors',
  48. },
  49. ],
  50. },
  51. ...((organization.features.includes('crash-rate-alerts')
  52. ? [
  53. {
  54. label: t('SESSIONS'),
  55. options: [
  56. {
  57. label: AlertWizardAlertNames.crash_free_sessions,
  58. value: 'crash_free_sessions',
  59. },
  60. {
  61. label: AlertWizardAlertNames.crash_free_users,
  62. value: 'crash_free_users',
  63. },
  64. ],
  65. },
  66. ]
  67. : []) as GroupedMenuOption[]),
  68. {
  69. label: t('PERFORMANCE'),
  70. options: [
  71. {
  72. label: AlertWizardAlertNames.throughput,
  73. value: 'throughput',
  74. },
  75. {
  76. label: AlertWizardAlertNames.trans_duration,
  77. value: 'trans_duration',
  78. },
  79. {
  80. label: AlertWizardAlertNames.apdex,
  81. value: 'apdex',
  82. },
  83. {
  84. label: AlertWizardAlertNames.failure_rate,
  85. value: 'failure_rate',
  86. },
  87. {
  88. label: AlertWizardAlertNames.lcp,
  89. value: 'lcp',
  90. },
  91. {
  92. label: AlertWizardAlertNames.fid,
  93. value: 'fid',
  94. },
  95. {
  96. label: AlertWizardAlertNames.cls,
  97. value: 'cls',
  98. },
  99. ],
  100. },
  101. {
  102. label: t('CUSTOM'),
  103. options: [
  104. {
  105. label: AlertWizardAlertNames.custom,
  106. value: 'custom',
  107. },
  108. ],
  109. },
  110. ];
  111. return (
  112. <FormField {...fieldProps}>
  113. {({onChange, model, disabled}) => {
  114. const aggregate = model.getValue('aggregate');
  115. const dataset: Dataset = model.getValue('dataset');
  116. const selectedTemplate: AlertType = alertType || 'custom';
  117. const {fieldOptionsConfig, hidePrimarySelector, hideParameterSelector} =
  118. getFieldOptionConfig({
  119. dataset: dataset as Dataset,
  120. alertType,
  121. });
  122. const fieldOptions = generateFieldOptions({organization, ...fieldOptionsConfig});
  123. const fieldValue = explodeFieldString(aggregate ?? '');
  124. const fieldKey =
  125. fieldValue?.kind === FieldValueKind.FUNCTION
  126. ? `function:${fieldValue.function[0]}`
  127. : '';
  128. const selectedField = fieldOptions[fieldKey]?.value;
  129. const numParameters: number =
  130. selectedField?.kind === FieldValueKind.FUNCTION
  131. ? selectedField.meta.parameters.length
  132. : 0;
  133. const gridColumns =
  134. 1 +
  135. numParameters -
  136. (hideParameterSelector ? 1 : 0) -
  137. (hidePrimarySelector ? 1 : 0);
  138. return (
  139. <Container hideGap={gridColumns < 1}>
  140. <SelectControl
  141. value={selectedTemplate}
  142. options={menuOptions}
  143. disabled={disabled}
  144. onChange={(option: MenuOption) => {
  145. const template = AlertWizardRuleTemplates[option.value];
  146. model.setValue('aggregate', template.aggregate);
  147. model.setValue('dataset', template.dataset);
  148. model.setValue('eventTypes', [template.eventTypes]);
  149. // Keep alertType last
  150. model.setValue('alertType', option.value);
  151. }}
  152. />
  153. <StyledQueryField
  154. filterPrimaryOptions={option =>
  155. option.value.kind === FieldValueKind.FUNCTION
  156. }
  157. fieldOptions={fieldOptions}
  158. fieldValue={fieldValue}
  159. onChange={v => onChange(generateFieldAsString(v), {})}
  160. columnWidth={columnWidth}
  161. gridColumns={gridColumns}
  162. inFieldLabels={inFieldLabels}
  163. shouldRenderTag={false}
  164. disabled={disabled}
  165. hideParameterSelector={hideParameterSelector}
  166. hidePrimarySelector={hidePrimarySelector}
  167. />
  168. </Container>
  169. );
  170. }}
  171. </FormField>
  172. );
  173. }
  174. const Container = styled('div')<{hideGap: boolean}>`
  175. display: grid;
  176. grid-template-columns: 1fr auto;
  177. gap: ${p => (p.hideGap ? space(0) : space(1))};
  178. `;
  179. const StyledQueryField = styled(QueryField)<{gridColumns: number; columnWidth?: number}>`
  180. ${p =>
  181. p.columnWidth &&
  182. css`
  183. width: ${p.gridColumns * p.columnWidth}px;
  184. `}
  185. `;