utils.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import IdBadge from 'sentry/components/idBadge';
  2. import {t} from 'sentry/locale';
  3. import type {IssueAlertRule} from 'sentry/types/alerts';
  4. import {IssueAlertActionType, RuleActionsCategories} from 'sentry/types/alerts';
  5. import type {Organization} from 'sentry/types/organization';
  6. import type {Project} from 'sentry/types/project';
  7. import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
  8. import {TIME_WINDOW_TO_INTERVAL} from 'sentry/views/alerts/rules/metric/triggers/chart';
  9. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  10. import {Dataset} from 'sentry/views/alerts/rules/metric/types';
  11. import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
  12. import {getExploreUrl} from 'sentry/views/explore/utils';
  13. import {ChartType} from 'sentry/views/insights/common/components/chart';
  14. export function getProjectOptions({
  15. organization,
  16. projects,
  17. isFormDisabled,
  18. }: {
  19. isFormDisabled: boolean;
  20. organization: Organization;
  21. projects: Project[];
  22. }) {
  23. const hasOrgAlertWrite = organization.access.includes('alerts:write');
  24. const hasOrgWrite = organization.access.includes('org:write');
  25. const hasOpenMembership = organization.features.includes('open-membership');
  26. // If form is enabled, we want to limit to the subset of projects which the
  27. // user can create/edit alerts.
  28. const projectWithWrite =
  29. isFormDisabled || hasOrgAlertWrite
  30. ? projects
  31. : projects.filter(project => project.access.includes('alerts:write'));
  32. const myProjects = projectWithWrite.filter(project => project.isMember);
  33. const allProjects = projectWithWrite.filter(project => !project.isMember);
  34. const myProjectOptions = myProjects.map(myProject => ({
  35. value: myProject.id,
  36. label: myProject.slug,
  37. leadingItems: renderIdBadge(myProject),
  38. }));
  39. const openMembershipProjects = [
  40. {
  41. label: t('My Projects'),
  42. options: myProjectOptions,
  43. },
  44. {
  45. label: t('All Projects'),
  46. options: allProjects.map(allProject => ({
  47. value: allProject.id,
  48. label: allProject.slug,
  49. leadingItems: renderIdBadge(allProject),
  50. })),
  51. },
  52. ];
  53. return hasOpenMembership || hasOrgWrite || isActiveSuperuser()
  54. ? openMembershipProjects
  55. : myProjectOptions;
  56. }
  57. function renderIdBadge(project: Project) {
  58. return (
  59. <IdBadge
  60. project={project}
  61. avatarProps={{consistentWidth: true}}
  62. avatarSize={18}
  63. disableLink
  64. hideName
  65. />
  66. );
  67. }
  68. export function getRuleActionCategory(rule: IssueAlertRule) {
  69. const numDefaultActions = rule.actions.filter(
  70. action => action.id === IssueAlertActionType.NOTIFY_EMAIL
  71. ).length;
  72. switch (numDefaultActions) {
  73. // Are all actions default actions?
  74. case rule.actions.length:
  75. return RuleActionsCategories.ALL_DEFAULT;
  76. // Are none of the actions default actions?
  77. case 0:
  78. return RuleActionsCategories.NO_DEFAULT;
  79. default:
  80. return RuleActionsCategories.SOME_DEFAULT;
  81. }
  82. }
  83. export function getAlertRuleActionCategory(rule: MetricRule) {
  84. const actions = rule.triggers.flatMap(trigger => trigger.actions);
  85. const numDefaultActions = actions.filter(action => action.type === 'email').length;
  86. switch (numDefaultActions) {
  87. // Are all actions default actions?
  88. case actions.length:
  89. return RuleActionsCategories.ALL_DEFAULT;
  90. // Are none of the actions default actions?
  91. case 0:
  92. return RuleActionsCategories.NO_DEFAULT;
  93. default:
  94. return RuleActionsCategories.SOME_DEFAULT;
  95. }
  96. }
  97. export function shouldUseErrorsDiscoverDataset(
  98. query: string,
  99. dataset: Dataset,
  100. organization: Organization
  101. ) {
  102. if (!hasDatasetSelector(organization)) {
  103. return dataset === Dataset.ERRORS && query?.includes('is:unresolved');
  104. }
  105. return dataset === Dataset.ERRORS;
  106. }
  107. export function getAlertRuleExploreUrl({
  108. rule,
  109. orgSlug,
  110. period,
  111. projectId,
  112. }: {
  113. orgSlug: string;
  114. period: string;
  115. projectId: string;
  116. rule: MetricRule;
  117. }) {
  118. if (rule.dataset !== Dataset.EVENTS_ANALYTICS_PLATFORM) {
  119. return '';
  120. }
  121. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  122. const interval = TIME_WINDOW_TO_INTERVAL[rule.timeWindow];
  123. return getExploreUrl({
  124. orgSlug,
  125. selection: {
  126. datetime: {
  127. period: period === '9998m' ? '7d' : period,
  128. start: null,
  129. end: null,
  130. utc: null,
  131. },
  132. environments: rule.environment ? [rule.environment] : [],
  133. projects: [parseInt(projectId, 10)],
  134. },
  135. interval,
  136. visualize: [
  137. {
  138. chartType: ChartType.LINE,
  139. yAxes: [rule.aggregate],
  140. },
  141. ],
  142. query: rule.query,
  143. });
  144. }