errorMigrationWarning.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 {Button, LinkButton} from 'sentry/components/button';
  9. import ButtonBar from 'sentry/components/buttonBar';
  10. import {Alert} from 'sentry/components/core/alert';
  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.Container>
  91. <Alert
  92. type="warning"
  93. showIcon
  94. trailingItems={
  95. <ButtonBar gap={1}>
  96. <LinkButton
  97. to={{
  98. pathname: `/organizations/${organization.slug}/alerts/metric-rules/${
  99. project?.slug ?? rule?.projects?.[0]
  100. }/${rule.id}/`,
  101. query: {migration: '1'},
  102. }}
  103. size="xs"
  104. icon={<IconEdit />}
  105. >
  106. {t('Exclude archived issues')}
  107. </LinkButton>
  108. <DismissButton
  109. priority="link"
  110. icon={<IconClose />}
  111. onClick={dismissPrompt}
  112. aria-label={t('Dismiss Alert')}
  113. title={t('Dismiss Alert')}
  114. />
  115. </ButtonBar>
  116. }
  117. >
  118. {t(
  119. "Alert rules can now exclude errors associated with archived issues. Please make sure to review the rule's alert thresholds after editing."
  120. )}
  121. </Alert>
  122. </Alert.Container>
  123. );
  124. }
  125. const DismissButton = styled(Button)`
  126. color: ${p => p.theme.alert.warning.color};
  127. pointer-events: all;
  128. &:hover {
  129. opacity: 0.5;
  130. }
  131. `;