metricIssuesSection.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {Fragment, useMemo} from 'react';
  2. import moment from 'moment-timezone';
  3. import {LinkButton} from 'sentry/components/button';
  4. import {DateTime} from 'sentry/components/dateTime';
  5. import {t} from 'sentry/locale';
  6. import type {Event} from 'sentry/types/event';
  7. import type {Group} from 'sentry/types/group';
  8. import type {Organization} from 'sentry/types/organization';
  9. import type {Project} from 'sentry/types/project';
  10. import {useApiQuery} from 'sentry/utils/queryClient';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
  13. import RelatedIssues from 'sentry/views/alerts/rules/metric/details/relatedIssues';
  14. import RelatedTransactions from 'sentry/views/alerts/rules/metric/details/relatedTransactions';
  15. import {
  16. Dataset,
  17. type MetricRule,
  18. TimePeriod,
  19. } from 'sentry/views/alerts/rules/metric/types';
  20. import {extractEventTypeFilterFromRule} from 'sentry/views/alerts/rules/metric/utils/getEventTypeFilter';
  21. import {isCrashFreeAlert} from 'sentry/views/alerts/rules/metric/utils/isCrashFreeAlert';
  22. import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
  23. import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
  24. interface MetricIssuesSectionProps {
  25. event: Event;
  26. group: Group;
  27. organization: Organization;
  28. project: Project;
  29. }
  30. export default function MetricIssuesSection({
  31. organization,
  32. event,
  33. group,
  34. project,
  35. }: MetricIssuesSectionProps) {
  36. const location = useLocation();
  37. const ruleId = event.contexts?.metric_alert?.alert_rule_id;
  38. const {data: rule} = useApiQuery<MetricRule>(
  39. [
  40. `/organizations/${organization.slug}/alert-rules/${ruleId}/`,
  41. {
  42. query: {
  43. expand: 'latestIncident',
  44. },
  45. },
  46. ],
  47. {
  48. staleTime: Infinity,
  49. retry: false,
  50. }
  51. );
  52. const openPeriod = group.openPeriods?.[0];
  53. const timePeriod = useMemo((): TimePeriodType | null => {
  54. if (!openPeriod) {
  55. return null;
  56. }
  57. const start = openPeriod.start;
  58. let end = openPeriod.end;
  59. if (!end) {
  60. end = new Date().toISOString();
  61. }
  62. return {
  63. start,
  64. end,
  65. period: TimePeriod.SEVEN_DAYS,
  66. usingPeriod: false,
  67. label: t('Custom time'),
  68. display: (
  69. <Fragment>
  70. <DateTime date={moment.utc(start)} />
  71. {' — '}
  72. <DateTime date={moment.utc(end)} />
  73. </Fragment>
  74. ),
  75. custom: true,
  76. };
  77. }, [openPeriod]);
  78. if (!rule || !timePeriod) {
  79. return null;
  80. }
  81. const {dataset, query} = rule;
  82. if ([Dataset.METRICS, Dataset.SESSIONS, Dataset.ERRORS].includes(dataset)) {
  83. const queryParams = {
  84. start: timePeriod.start,
  85. end: timePeriod.end,
  86. groupStatsPeriod: 'auto',
  87. ...(rule.environment ? {environment: rule.environment} : {}),
  88. sort: rule.aggregate === 'count_unique(user)' ? 'user' : 'freq',
  89. query,
  90. project: [project.id],
  91. };
  92. const issueSearch = {
  93. pathname: `/organizations/${organization.slug}/issues/`,
  94. query: queryParams,
  95. };
  96. const actions = (
  97. <LinkButton data-test-id="issues-open" size="xs" to={issueSearch}>
  98. {t('Open in Issues')}
  99. </LinkButton>
  100. );
  101. return (
  102. <InterimSection
  103. title={t('Correlated Issues')}
  104. type={SectionKey.CORRELATED_ISSUES}
  105. help={t('A list of issues that are correlated with this event')}
  106. actions={actions}
  107. >
  108. <RelatedIssues
  109. organization={organization}
  110. rule={rule}
  111. projects={[project]}
  112. timePeriod={timePeriod}
  113. query={
  114. dataset === Dataset.ERRORS
  115. ? // Not using (query) AND (event.type:x) because issues doesn't support it yet
  116. `${extractEventTypeFilterFromRule(rule)} ${query}`.trim()
  117. : isCrashFreeAlert(dataset)
  118. ? `${query} error.unhandled:true`.trim()
  119. : undefined
  120. }
  121. skipHeader
  122. />
  123. </InterimSection>
  124. );
  125. }
  126. if ([Dataset.TRANSACTIONS, Dataset.GENERIC_METRICS].includes(dataset)) {
  127. return (
  128. <InterimSection
  129. title={t('Correlated Transactions')}
  130. type={SectionKey.CORRELATED_TRANSACTIONS}
  131. help={t('A list of transactions that are correlated with this event')}
  132. >
  133. <RelatedTransactions
  134. organization={organization}
  135. location={location}
  136. rule={rule}
  137. projects={[project]}
  138. timePeriod={timePeriod}
  139. filter={extractEventTypeFilterFromRule(rule)}
  140. />
  141. </InterimSection>
  142. );
  143. }
  144. return null;
  145. }