wizardField.tsx 6.3 KB

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