import {useState} from 'react'; import styled from '@emotion/styled'; import pick from 'lodash/pick'; import xor from 'lodash/xor'; import {addErrorMessage} from 'sentry/actionCreators/indicator'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Pagination from 'sentry/components/pagination'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {IssueAttachment, Project} from 'sentry/types'; import {useApiQuery, useMutation} from 'sentry/utils/queryClient'; import {decodeList} from 'sentry/utils/queryString'; import useApi from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; import {useParams} from 'sentry/utils/useParams'; import GroupEventAttachmentsFilter, { crashReportTypes, SCREENSHOT_TYPE, } from './groupEventAttachmentsFilter'; import GroupEventAttachmentsTable from './groupEventAttachmentsTable'; import {ScreenshotCard} from './screenshotCard'; type GroupEventAttachmentsProps = { project: Project; }; enum EventAttachmentFilter { ALL = 'all', CRASH_REPORTS = 'onlyCrash', SCREENSHOTS = 'screenshot', } export const MAX_SCREENSHOTS_PER_PAGE = 12; function useActiveAttachmentsTab() { const location = useLocation(); const types = decodeList(location.query.types); if (types.length === 0) { return EventAttachmentFilter.ALL; } if (types[0] === SCREENSHOT_TYPE) { return EventAttachmentFilter.SCREENSHOTS; } if (xor(crashReportTypes, types).length === 0) { return EventAttachmentFilter.CRASH_REPORTS; } return EventAttachmentFilter.ALL; } function GroupEventAttachments({project}: GroupEventAttachmentsProps) { const location = useLocation(); const {groupId, orgId} = useParams<{groupId: string; orgId: string}>(); const activeAttachmentsTab = useActiveAttachmentsTab(); const [deletedAttachments, setDeletedAttachments] = useState([]); const api = useApi(); const { data: eventAttachments, isLoading, isError, getResponseHeader, refetch, } = useApiQuery( [ `/organizations/${orgId}/issues/${groupId}/attachments/`, { query: activeAttachmentsTab === EventAttachmentFilter.SCREENSHOTS ? { ...location.query, types: undefined, // need to explicitly set this to undefined because AsyncComponent adds location query back into the params screenshot: 1, per_page: MAX_SCREENSHOTS_PER_PAGE, } : { ...pick(location.query, ['cursor', 'environment', 'types']), per_page: 50, }, }, ], {staleTime: 0} ); const {mutate: deleteAttachment} = useMutation({ mutationFn: ({attachmentId, eventId}: {attachmentId: string; eventId: string}) => api.requestPromise( `/projects/${orgId}/${project.slug}/events/${eventId}/attachments/${attachmentId}/`, { method: 'DELETE', } ), onError: () => { addErrorMessage('An error occurred while deleteting the attachment'); }, }); const handleDelete = (deletedAttachmentId: string) => { const attachment = eventAttachments?.find(item => item.id === deletedAttachmentId); if (!attachment) { return; } setDeletedAttachments(prevState => [...prevState, deletedAttachmentId]); deleteAttachment({attachmentId: attachment.id, eventId: attachment.event_id}); }; const renderInnerBody = () => { if (isLoading) { return ; } if (eventAttachments && eventAttachments.length > 0) { return ( ); } if (activeAttachmentsTab === EventAttachmentFilter.CRASH_REPORTS) { return (

{t('No crash reports found')}

); } return (

{t('No attachments found')}

); }; const renderAttachmentsTable = () => { if (isError) { return ; } return ( {renderInnerBody()} ); }; const renderScreenshotGallery = () => { if (isError) { return ; } if (isLoading) { return ; } if (eventAttachments && eventAttachments.length > 0) { return ( {eventAttachments?.map((screenshot, index) => { return ( ); })} ); } return (

{t('No screenshots found')}

); }; return ( {activeAttachmentsTab === EventAttachmentFilter.SCREENSHOTS ? renderScreenshotGallery() : renderAttachmentsTable()} ); } export default GroupEventAttachments; const ScreenshotGrid = styled('div')` display: grid; grid-template-columns: minmax(100px, 1fr); grid-template-rows: repeat(2, max-content); gap: ${space(2)}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: repeat(3, minmax(100px, 1fr)); } @media (min-width: ${p => p.theme.breakpoints.large}) { grid-template-columns: repeat(4, minmax(100px, 1fr)); } @media (min-width: ${p => p.theme.breakpoints.xxlarge}) { grid-template-columns: repeat(6, minmax(100px, 1fr)); } `;