123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- 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} from 'sentry/types/group';
- import type {Project} from 'sentry/types/project';
- 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<string[]>([]);
- const api = useApi();
- const {
- data: eventAttachments,
- isPending,
- isError,
- getResponseHeader,
- refetch,
- } = useApiQuery<IssueAttachment[]>(
- [
- `/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 (isPending) {
- return <LoadingIndicator />;
- }
- if (eventAttachments && eventAttachments.length > 0) {
- return (
- <GroupEventAttachmentsTable
- attachments={eventAttachments}
- orgId={orgId}
- projectSlug={project.slug}
- groupId={groupId}
- onDelete={handleDelete}
- deletedAttachments={deletedAttachments}
- />
- );
- }
- if (activeAttachmentsTab === EventAttachmentFilter.CRASH_REPORTS) {
- return (
- <EmptyStateWarning>
- <p>{t('No crash reports found')}</p>
- </EmptyStateWarning>
- );
- }
- return (
- <EmptyStateWarning>
- <p>{t('No attachments found')}</p>
- </EmptyStateWarning>
- );
- };
- const renderAttachmentsTable = () => {
- if (isError) {
- return <LoadingError onRetry={refetch} message={t('Error loading attachments')} />;
- }
- return (
- <Panel className="event-list">
- <PanelBody>{renderInnerBody()}</PanelBody>
- </Panel>
- );
- };
- const renderScreenshotGallery = () => {
- if (isError) {
- return <LoadingError onRetry={refetch} message={t('Error loading screenshots')} />;
- }
- if (isPending) {
- return <LoadingIndicator />;
- }
- if (eventAttachments && eventAttachments.length > 0) {
- return (
- <ScreenshotGrid>
- {eventAttachments?.map((screenshot, index) => {
- return (
- <ScreenshotCard
- key={`${index}-${screenshot.id}`}
- eventAttachment={screenshot}
- eventId={screenshot.event_id}
- projectSlug={project.slug}
- groupId={groupId}
- onDelete={handleDelete}
- pageLinks={getResponseHeader?.('Link')}
- attachments={eventAttachments}
- attachmentIndex={index}
- />
- );
- })}
- </ScreenshotGrid>
- );
- }
- return (
- <EmptyStateWarning>
- <p>{t('No screenshots found')}</p>
- </EmptyStateWarning>
- );
- };
- return (
- <Layout.Body>
- <Layout.Main fullWidth>
- <GroupEventAttachmentsFilter project={project} />
- {activeAttachmentsTab === EventAttachmentFilter.SCREENSHOTS
- ? renderScreenshotGallery()
- : renderAttachmentsTable()}
- <Pagination pageLinks={getResponseHeader?.('Link')} />
- </Layout.Main>
- </Layout.Body>
- );
- }
- 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));
- }
- `;
|