issueList.tsx 4.2 KB

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