issueList.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {Fragment, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import EventOrGroupExtraDetails from 'sentry/components/eventOrGroupExtraDetails';
  5. import EventOrGroupHeader from 'sentry/components/eventOrGroupHeader';
  6. import {PanelTable} from 'sentry/components/panels';
  7. import ReplayCountContext from 'sentry/components/replays/replayCountContext';
  8. import useReplaysCount from 'sentry/components/replays/useReplaysCount';
  9. import {DEFAULT_STREAM_GROUP_STATS_PERIOD} from 'sentry/components/stream/group';
  10. import GroupChart from 'sentry/components/stream/groupChart';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import {Group, Organization} from 'sentry/types';
  14. import {useQuery} from 'sentry/utils/queryClient';
  15. import RequestError from 'sentry/utils/requestError/requestError';
  16. import theme from 'sentry/utils/theme';
  17. import useApi from 'sentry/utils/useApi';
  18. import useMedia from 'sentry/utils/useMedia';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. type Props = {
  21. projectId: string;
  22. replayId: string;
  23. };
  24. const columns = [t('Issue'), t('Graph'), t('Events'), t('Users')];
  25. function IssueList({projectId, replayId}: Props) {
  26. const api = useApi();
  27. const organization = useOrganization();
  28. const isScreenLarge = useMedia(`(min-width: ${theme.breakpoints.large})`);
  29. const url = `/organizations/${organization.slug}/issues/`;
  30. const query = {
  31. query: `replayId:${replayId}`,
  32. };
  33. const {
  34. data: issues = [],
  35. isLoading,
  36. isError,
  37. error,
  38. } = useQuery<Group[], RequestError>({
  39. queryKey: [url, query],
  40. queryFn: () =>
  41. api.requestPromise(url, {
  42. query,
  43. headers: {
  44. 'x-sentry-replay-request': '1',
  45. },
  46. }),
  47. });
  48. useEffect(() => {
  49. if (!isError) {
  50. return;
  51. }
  52. Sentry.captureException(error);
  53. }, [isError, error]);
  54. const counts = useReplaysCount({
  55. groupIds: issues.map(issue => issue.id),
  56. organization,
  57. });
  58. return (
  59. <ReplayCountContext.Provider value={counts}>
  60. <StyledPanelTable
  61. isEmpty={issues.length === 0}
  62. emptyMessage={t('No Issues are related')}
  63. isLoading={isLoading}
  64. headers={
  65. isScreenLarge ? columns : columns.filter(column => column !== t('Graph'))
  66. }
  67. >
  68. {issues
  69. // prioritize the replay issues for the project first, followed by first_seen
  70. .sort((a, b) => {
  71. if (a.project.id === projectId) {
  72. if (a.project.id === b.project.id) {
  73. return new Date(a.firstSeen).getTime() - new Date(b.firstSeen).getTime();
  74. }
  75. return -1;
  76. }
  77. return 1;
  78. })
  79. .map(issue => (
  80. <TableRow
  81. key={issue.id}
  82. isScreenLarge={isScreenLarge}
  83. issue={issue}
  84. organization={organization}
  85. />
  86. )) || null}
  87. </StyledPanelTable>
  88. </ReplayCountContext.Provider>
  89. );
  90. }
  91. function TableRow({
  92. isScreenLarge,
  93. issue,
  94. organization,
  95. }: {
  96. isScreenLarge: boolean;
  97. issue: Group;
  98. organization: Organization;
  99. }) {
  100. return (
  101. <Fragment>
  102. <IssueDetailsWrapper>
  103. <EventOrGroupHeader
  104. data={issue}
  105. organization={organization}
  106. size="normal"
  107. source="replay"
  108. />
  109. <EventOrGroupExtraDetails data={issue} />
  110. </IssueDetailsWrapper>
  111. {isScreenLarge && (
  112. <ChartWrapper>
  113. <GroupChart
  114. statsPeriod={DEFAULT_STREAM_GROUP_STATS_PERIOD}
  115. data={issue}
  116. showSecondaryPoints
  117. showMarkLine
  118. />
  119. </ChartWrapper>
  120. )}
  121. <Item>{issue.count}</Item>
  122. <Item>{issue.userCount}</Item>
  123. </Fragment>
  124. );
  125. }
  126. const ChartWrapper = styled('div')`
  127. width: 200px;
  128. margin-left: -${space(2)};
  129. padding-left: ${space(0)};
  130. `;
  131. const Item = styled('div')`
  132. display: flex;
  133. align-items: center;
  134. `;
  135. const IssueDetailsWrapper = styled('div')`
  136. overflow: hidden;
  137. line-height: normal;
  138. `;
  139. const StyledPanelTable = styled(PanelTable)`
  140. /* overflow: visible allows the tooltip to be completely shown */
  141. overflow: visible;
  142. grid-template-columns: minmax(1fr, max-content) repeat(3, max-content);
  143. @media (max-width: ${p => p.theme.breakpoints.large}) {
  144. grid-template-columns: minmax(0, 1fr) repeat(2, max-content);
  145. }
  146. `;
  147. export default IssueList;