errorMigrationWarning.tsx 4.2 KB

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