issueList.tsx 4.1 KB

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