groupEventAttachments.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import styled from '@emotion/styled';
  2. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  3. import LoadingError from 'sentry/components/loadingError';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import Pagination from 'sentry/components/pagination';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {IssueAttachment} from 'sentry/types/group';
  9. import type {Project} from 'sentry/types/project';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import GroupEventAttachmentsFilter, {
  13. EventAttachmentFilter,
  14. } from './groupEventAttachmentsFilter';
  15. import GroupEventAttachmentsTable from './groupEventAttachmentsTable';
  16. import {ScreenshotCard} from './screenshotCard';
  17. import {useDeleteGroupEventAttachment} from './useDeleteGroupEventAttachment';
  18. import {useGroupEventAttachments} from './useGroupEventAttachments';
  19. type GroupEventAttachmentsProps = {
  20. groupId: string;
  21. project: Project;
  22. };
  23. function GroupEventAttachments({project, groupId}: GroupEventAttachmentsProps) {
  24. const location = useLocation();
  25. const organization = useOrganization();
  26. const activeAttachmentsTab =
  27. (location.query.attachmentFilter as EventAttachmentFilter | undefined) ??
  28. EventAttachmentFilter.ALL;
  29. const {attachments, isPending, isError, getResponseHeader, refetch} =
  30. useGroupEventAttachments({
  31. groupId,
  32. activeAttachmentsTab,
  33. });
  34. const {mutate: deleteAttachment} = useDeleteGroupEventAttachment();
  35. const handleDelete = (attachment: IssueAttachment) => {
  36. deleteAttachment({
  37. attachment,
  38. groupId,
  39. orgSlug: organization.slug,
  40. activeAttachmentsTab,
  41. projectSlug: project.slug,
  42. cursor: location.query.cursor as string | undefined,
  43. environment: location.query.environment as string[] | string | undefined,
  44. });
  45. };
  46. const renderAttachmentsTable = () => {
  47. if (isError) {
  48. return <LoadingError onRetry={refetch} message={t('Error loading attachments')} />;
  49. }
  50. return (
  51. <GroupEventAttachmentsTable
  52. isLoading={isPending}
  53. attachments={attachments}
  54. projectSlug={project.slug}
  55. groupId={groupId}
  56. onDelete={handleDelete}
  57. emptyMessage={
  58. activeAttachmentsTab === EventAttachmentFilter.CRASH_REPORTS
  59. ? t('No crash reports found')
  60. : t('No attachments found')
  61. }
  62. />
  63. );
  64. };
  65. const renderScreenshotGallery = () => {
  66. if (isError) {
  67. return <LoadingError onRetry={refetch} message={t('Error loading screenshots')} />;
  68. }
  69. if (isPending) {
  70. return <LoadingIndicator />;
  71. }
  72. if (attachments.length > 0) {
  73. return (
  74. <ScreenshotGrid>
  75. {attachments.map(screenshot => {
  76. return (
  77. <ScreenshotCard
  78. key={screenshot.id}
  79. eventAttachment={screenshot}
  80. eventId={screenshot.event_id}
  81. projectSlug={project.slug}
  82. groupId={groupId}
  83. onDelete={handleDelete}
  84. attachments={attachments}
  85. />
  86. );
  87. })}
  88. </ScreenshotGrid>
  89. );
  90. }
  91. return (
  92. <EmptyStateWarning>
  93. <p>{t('No screenshots found')}</p>
  94. </EmptyStateWarning>
  95. );
  96. };
  97. return (
  98. <Wrapper>
  99. <GroupEventAttachmentsFilter project={project} />
  100. {activeAttachmentsTab === EventAttachmentFilter.SCREENSHOT
  101. ? renderScreenshotGallery()
  102. : renderAttachmentsTable()}
  103. <NoMarginPagination pageLinks={getResponseHeader?.('Link')} />
  104. </Wrapper>
  105. );
  106. }
  107. export default GroupEventAttachments;
  108. const ScreenshotGrid = styled('div')`
  109. display: grid;
  110. grid-template-columns: minmax(100px, 1fr);
  111. grid-template-rows: repeat(2, max-content);
  112. gap: ${space(2)};
  113. @media (min-width: ${p => p.theme.breakpoints.small}) {
  114. grid-template-columns: repeat(3, minmax(100px, 1fr));
  115. }
  116. @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
  117. grid-template-columns: repeat(4, minmax(100px, 1fr));
  118. }
  119. @media (min-width: ${p => p.theme.breakpoints.xxlarge}) {
  120. grid-template-columns: repeat(6, minmax(100px, 1fr));
  121. }
  122. `;
  123. const NoMarginPagination = styled(Pagination)`
  124. margin: 0;
  125. `;
  126. const Wrapper = styled('div')`
  127. display: flex;
  128. flex-direction: column;
  129. gap: ${space(2)};
  130. `;