errorMigrationWarning.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import styled from '@emotion/styled';
  2. import type {PromptResponse} from 'sentry/actionCreators/prompts';
  3. import {
  4. makePromptsCheckQueryKey,
  5. promptsUpdate,
  6. usePromptsCheck,
  7. } from 'sentry/actionCreators/prompts';
  8. import Alert from 'sentry/components/alert';
  9. import {Button, LinkButton} from 'sentry/components/button';
  10. import ButtonBar from 'sentry/components/buttonBar';
  11. import {IconClose, IconEdit} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import type {Project} from 'sentry/types';
  14. import {promptIsDismissed} from 'sentry/utils/promptIsDismissed';
  15. import {setApiQueryData, useQueryClient} from 'sentry/utils/queryClient';
  16. import useApi from 'sentry/utils/useApi';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import type {MetricRule} from 'sentry/views/alerts/rules/metric/types';
  19. import {
  20. hasIgnoreArchivedFeatureFlag,
  21. ruleNeedsErrorMigration,
  22. } from 'sentry/views/alerts/utils/migrationUi';
  23. interface ErrorMigrationWarningProps {
  24. project?: Project;
  25. rule?: MetricRule;
  26. }
  27. const METRIC_ALERT_IGNORE_ARCHIVED_ISSUES = 'metric_alert_ignore_archived_issues';
  28. function createdOrModifiedAfterMigration(rule: MetricRule) {
  29. const migrationDate = new Date('2023-12-11T00:00:00Z').getTime();
  30. return (
  31. (rule.dateCreated && new Date(rule.dateCreated).getTime() > migrationDate) ||
  32. (rule.dateModified && new Date(rule.dateModified).getTime() > migrationDate)
  33. );
  34. }
  35. /**
  36. * Displays a message to filter events from archived issues when the metric alert
  37. * is counting error events.
  38. */
  39. export function ErrorMigrationWarning({project, rule}: ErrorMigrationWarningProps) {
  40. const api = useApi();
  41. const organization = useOrganization();
  42. const queryClient = useQueryClient();
  43. const showErrorMigrationWarning =
  44. rule && hasIgnoreArchivedFeatureFlag(organization) && ruleNeedsErrorMigration(rule);
  45. const isCreatedAfterMigration = rule && createdOrModifiedAfterMigration(rule);
  46. const prompt = usePromptsCheck(
  47. {
  48. organization,
  49. feature: METRIC_ALERT_IGNORE_ARCHIVED_ISSUES,
  50. projectId: project?.id,
  51. },
  52. {staleTime: Infinity, enabled: showErrorMigrationWarning && !isCreatedAfterMigration}
  53. );
  54. const isPromptDismissed =
  55. prompt.isSuccess && prompt.data.data
  56. ? promptIsDismissed({
  57. dismissedTime: prompt.data.data.dismissed_ts,
  58. snoozedTime: prompt.data.data.snoozed_ts,
  59. })
  60. : false;
  61. if (
  62. !showErrorMigrationWarning ||
  63. !rule ||
  64. isPromptDismissed ||
  65. isCreatedAfterMigration
  66. ) {
  67. return null;
  68. }
  69. const dismissPrompt = () => {
  70. promptsUpdate(api, {
  71. organization,
  72. projectId: project?.id,
  73. feature: METRIC_ALERT_IGNORE_ARCHIVED_ISSUES,
  74. status: 'dismissed',
  75. });
  76. // Update cached query data, set to dismissed
  77. setApiQueryData<PromptResponse>(
  78. queryClient,
  79. makePromptsCheckQueryKey({
  80. organization,
  81. feature: METRIC_ALERT_IGNORE_ARCHIVED_ISSUES,
  82. projectId: project?.id,
  83. }),
  84. () => {
  85. const dimissedTs = new Date().getTime() / 1000;
  86. return {
  87. data: {dismissed_ts: dimissedTs},
  88. features: {[METRIC_ALERT_IGNORE_ARCHIVED_ISSUES]: {dismissed_ts: dimissedTs}},
  89. };
  90. }
  91. );
  92. };
  93. return (
  94. <Alert
  95. type="warning"
  96. showIcon
  97. trailingItems={
  98. <ButtonBar gap={1}>
  99. <LinkButton
  100. to={{
  101. pathname: `/organizations/${organization.slug}/alerts/metric-rules/${
  102. project?.slug ?? rule?.projects?.[0]
  103. }/${rule.id}/`,
  104. query: {migration: '1'},
  105. }}
  106. size="xs"
  107. icon={<IconEdit />}
  108. >
  109. {t('Exclude archived issues')}
  110. </LinkButton>
  111. <DismissButton
  112. priority="link"
  113. icon={<IconClose />}
  114. onClick={dismissPrompt}
  115. aria-label={t('Dismiss Alert')}
  116. title={t('Dismiss Alert')}
  117. />
  118. </ButtonBar>
  119. }
  120. >
  121. {t(
  122. "Alert rules can now exclude errors associated with archived issues. Please make sure to review the rule's alert thresholds after editing."
  123. )}
  124. </Alert>
  125. );
  126. }
  127. const DismissButton = styled(Button)`
  128. color: ${p => p.theme.alert.warning.iconColor};
  129. pointer-events: all;
  130. &:hover {
  131. color: ${p => p.theme.alert.warning.iconHoverColor};
  132. opacity: 0.5;
  133. }
  134. `;