import {WithRouterProps} from 'react-router'; import styled from '@emotion/styled'; import pick from 'lodash/pick'; import xor from 'lodash/xor'; import {addErrorMessage} from 'sentry/actionCreators/indicator'; import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; import EmptyStateWarning from 'sentry/components/emptyStateWarning'; import * as Layout from 'sentry/components/layouts/thirds'; 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 {IssueAttachment, Project} from 'sentry/types'; import {decodeList} from 'sentry/utils/queryString'; // eslint-disable-next-line no-restricted-imports import withSentryRouter from 'sentry/utils/withSentryRouter'; import GroupEventAttachmentsFilter, { crashReportTypes, SCREENSHOT_TYPE, } from './groupEventAttachmentsFilter'; import GroupEventAttachmentsTable from './groupEventAttachmentsTable'; import {ScreenshotCard} from './screenshotCard'; type Props = { project: Project; } & WithRouterProps<{groupId: string; orgId: string}> & DeprecatedAsyncComponent['props']; enum EventAttachmentFilter { ALL = 'all', CRASH_REPORTS = 'onlyCrash', SCREENSHOTS = 'screenshot', } type State = { deletedAttachments: string[]; eventAttachments?: IssueAttachment[]; } & DeprecatedAsyncComponent['state']; export const MAX_SCREENSHOTS_PER_PAGE = 12; class GroupEventAttachments extends DeprecatedAsyncComponent { getDefaultState() { return { ...super.getDefaultState(), deletedAttachments: [], }; } getActiveAttachmentsTab() { const {location} = this.props; 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; } getEndpoints(): ReturnType { const {params, location} = this.props; if (this.getActiveAttachmentsTab() === EventAttachmentFilter.SCREENSHOTS) { return [ [ 'eventAttachments', `/issues/${params.groupId}/attachments/`, { query: { ...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, }, }, ], ]; } return [ [ 'eventAttachments', `/issues/${params.groupId}/attachments/`, { query: { ...pick(location.query, ['cursor', 'environment', 'types']), per_page: 50, }, }, ], ]; } handleDelete = async (deletedAttachmentId: string) => { const {params, project} = this.props; const attachment = this.state?.eventAttachments?.find( item => item.id === deletedAttachmentId ); if (!attachment) { return; } this.setState(prevState => ({ deletedAttachments: [...prevState.deletedAttachments, deletedAttachmentId], })); try { await this.api.requestPromise( `/projects/${params.orgId}/${project.slug}/events/${attachment.event_id}/attachments/${attachment.id}/`, { method: 'DELETE', } ); } catch (error) { addErrorMessage('An error occurred while deleteting the attachment'); } }; renderNoQueryResults() { return (

{t('No crash reports found')}

); } renderNoScreenshotsResults() { return (

{t('No screenshots found')}

); } renderEmpty() { return (

{t('No attachments found')}

); } renderLoading() { return this.renderBody(); } renderInnerBody() { const {project, params} = this.props; const {loading, eventAttachments, deletedAttachments} = this.state; if (loading) { return ; } if (eventAttachments && eventAttachments.length > 0) { return ( ); } if (this.getActiveAttachmentsTab() === EventAttachmentFilter.CRASH_REPORTS) { return this.renderNoQueryResults(); } return this.renderEmpty(); } renderScreenshotGallery() { const {eventAttachments, loading} = this.state; const {project, params} = this.props; if (loading) { return ; } if (eventAttachments && eventAttachments.length > 0) { return ( {eventAttachments?.map((screenshot, index) => { return ( ); })} ); } return this.renderNoScreenshotsResults(); } renderAttachmentsTable() { return ( {this.renderInnerBody()} ); } renderBody() { const {project} = this.props; return ( {this.getActiveAttachmentsTab() === EventAttachmentFilter.SCREENSHOTS ? this.renderScreenshotGallery() : this.renderAttachmentsTable()} ); } } export default withSentryRouter(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)); } `;