Browse Source

ref(event-details): Convert EventAttachments to an FC (#43803)

Malachi Willey 2 years ago
parent
commit
e13d417aa9

+ 150 - 135
static/app/components/events/eventAttachments.tsx

@@ -1,6 +1,5 @@
-import {Component, Fragment} from 'react';
+import {Fragment, useState} from 'react';
 import styled from '@emotion/styled';
-import {Location} from 'history';
 
 import AttachmentUrl from 'sentry/components/events/attachmentUrl';
 import ImageViewer from 'sentry/components/events/attachmentViewers/imageViewer';
@@ -14,160 +13,176 @@ import {PanelTable} from 'sentry/components/panels';
 import {t} from 'sentry/locale';
 import {IssueAttachment} from 'sentry/types';
 import {Event} from 'sentry/types/event';
+import {useLocation} from 'sentry/utils/useLocation';
+import useOrganization from 'sentry/utils/useOrganization';
 
 import EventAttachmentsCrashReportsNotice from './eventAttachmentsCrashReportsNotice';
 
-type Props = {
+type EventAttachmentsProps = {
   attachments: IssueAttachment[];
   event: Event;
-  location: Location;
   onDeleteAttachment: (attachmentId: IssueAttachment['id']) => void;
-  orgId: string;
   projectSlug: string;
 };
 
-type State = {
-  attachmentPreviews: Record<string, boolean>;
-  expanded: boolean;
-};
+type AttachmentPreviewOpenMap = Record<string, boolean>;
 
-export class EventAttachments extends Component<Props, State> {
-  state: State = {
-    expanded: false,
-    attachmentPreviews: {},
-  };
+interface InlineAttachmentsProps
+  extends Pick<EventAttachmentsProps, 'event' | 'projectSlug'> {
+  attachment: IssueAttachment;
+  attachmentPreviews: AttachmentPreviewOpenMap;
+}
 
-  getInlineAttachmentRenderer(attachment: IssueAttachment) {
-    switch (attachment.mimetype) {
-      case 'text/plain':
-        return attachment.size > 0 ? LogFileViewer : undefined;
-      case 'text/json':
-      case 'text/x-json':
-      case 'application/json':
-        if (attachment.name === 'rrweb.json' || attachment.name.startsWith('rrweb-')) {
-          return RRWebJsonViewer;
-        }
-        return JsonViewer;
-      case 'image/jpeg':
-      case 'image/png':
-      case 'image/gif':
-        return ImageViewer;
-      default:
-        return undefined;
-    }
+const getInlineAttachmentRenderer = (attachment: IssueAttachment) => {
+  switch (attachment.mimetype) {
+    case 'text/plain':
+      return attachment.size > 0 ? LogFileViewer : undefined;
+    case 'text/json':
+    case 'text/x-json':
+    case 'application/json':
+      if (attachment.name === 'rrweb.json' || attachment.name.startsWith('rrweb-')) {
+        return RRWebJsonViewer;
+      }
+      return JsonViewer;
+    case 'image/jpeg':
+    case 'image/png':
+    case 'image/gif':
+      return ImageViewer;
+    default:
+      return undefined;
   }
+};
+
+const hasInlineAttachmentRenderer = (attachment: IssueAttachment): boolean => {
+  return !!getInlineAttachmentRenderer(attachment);
+};
+
+const attachmentPreviewIsOpen = (
+  attachmentPreviews: Record<string, boolean>,
+  attachment: IssueAttachment
+) => {
+  return attachmentPreviews[attachment.id] === true;
+};
 
-  hasInlineAttachmentRenderer(attachment: IssueAttachment): boolean {
-    return !!this.getInlineAttachmentRenderer(attachment);
+const InlineEventAttachment = ({
+  attachmentPreviews,
+  attachment,
+  projectSlug,
+  event,
+}: InlineAttachmentsProps) => {
+  const organization = useOrganization();
+  const AttachmentComponent = getInlineAttachmentRenderer(attachment);
+
+  if (!AttachmentComponent || !attachmentPreviewIsOpen(attachmentPreviews, attachment)) {
+    return null;
   }
 
-  attachmentPreviewIsOpen = (attachment: IssueAttachment) => {
-    return !!this.state.attachmentPreviews[attachment.id];
-  };
+  return (
+    <AttachmentPreviewWrapper>
+      <AttachmentComponent
+        orgId={organization.slug}
+        projectSlug={projectSlug}
+        eventId={event.id}
+        attachment={attachment}
+      />
+    </AttachmentPreviewWrapper>
+  );
+};
 
-  renderInlineAttachment(attachment: IssueAttachment) {
-    const AttachmentComponent = this.getInlineAttachmentRenderer(attachment);
-    if (!AttachmentComponent || !this.attachmentPreviewIsOpen(attachment)) {
-      return null;
-    }
-    return (
-      <AttachmentPreviewWrapper>
-        <AttachmentComponent
-          orgId={this.props.orgId}
-          projectSlug={this.props.projectSlug}
-          eventId={this.props.event.id}
-          attachment={attachment}
-        />
-      </AttachmentPreviewWrapper>
-    );
+export const EventAttachments = ({
+  attachments,
+  event,
+  onDeleteAttachment,
+  projectSlug,
+}: EventAttachmentsProps) => {
+  const organization = useOrganization();
+  const location = useLocation();
+  const [attachmentPreviews, setAttachmentPreviews] = useState<AttachmentPreviewOpenMap>(
+    {}
+  );
+  const crashFileStripped = event.metadata.stripped_crash;
+
+  if (!attachments.length && !crashFileStripped) {
+    return null;
   }
 
-  togglePreview(attachment: IssueAttachment) {
-    this.setState(({attachmentPreviews}) => ({
-      attachmentPreviews: {
-        ...attachmentPreviews,
-        [attachment.id]: !attachmentPreviews[attachment.id],
-      },
+  const title = t('Attachments (%s)', attachments.length);
+
+  const lastAttachment = attachments.at(-1);
+  const lastAttachmentPreviewed =
+    lastAttachment && attachmentPreviewIsOpen(attachmentPreviews, lastAttachment);
+
+  const togglePreview = (attachment: IssueAttachment) => {
+    setAttachmentPreviews(previewsMap => ({
+      ...previewsMap,
+      [attachment.id]: !previewsMap[attachment.id],
     }));
-  }
+  };
 
-  render() {
-    const {event, projectSlug, orgId, location, attachments, onDeleteAttachment} =
-      this.props;
-    const crashFileStripped = event.metadata.stripped_crash;
-
-    if (!attachments.length && !crashFileStripped) {
-      return null;
-    }
-
-    const title = t('Attachments (%s)', attachments.length);
-
-    const lastAttachmentPreviewed =
-      attachments.length > 0 &&
-      this.attachmentPreviewIsOpen(attachments[attachments.length - 1]);
-
-    return (
-      <EventDataSection type="attachments" title={title}>
-        {crashFileStripped && (
-          <EventAttachmentsCrashReportsNotice
-            orgSlug={orgId}
-            projectSlug={projectSlug}
-            groupId={event.groupID!}
-            location={location}
-          />
-        )}
-
-        {attachments.length > 0 && (
-          <StyledPanelTable
-            headers={[
-              <Name key="name">{t('File Name')}</Name>,
-              <Size key="size">{t('Size')}</Size>,
-              t('Actions'),
-            ]}
-          >
-            {attachments.map(attachment => (
-              <Fragment key={attachment.id}>
-                <Name>{attachment.name}</Name>
-                <Size>
-                  <FileSize bytes={attachment.size} />
-                </Size>
-                <AttachmentUrl
-                  projectSlug={projectSlug}
-                  eventId={event.id}
-                  attachment={attachment}
-                >
-                  {url => (
-                    <div>
-                      <EventAttachmentActions
-                        url={url}
-                        onDelete={onDeleteAttachment}
-                        onPreview={_attachmentId => this.togglePreview(attachment)}
-                        withPreviewButton
-                        previewIsOpen={this.attachmentPreviewIsOpen(attachment)}
-                        hasPreview={this.hasInlineAttachmentRenderer(attachment)}
-                        attachmentId={attachment.id}
-                      />
-                    </div>
-                  )}
-                </AttachmentUrl>
-                {this.renderInlineAttachment(attachment)}
-                {/* XXX: hack to deal with table grid borders */}
-                {lastAttachmentPreviewed && (
-                  <Fragment>
-                    <div style={{display: 'none'}} />
-                    <div style={{display: 'none'}} />
-                  </Fragment>
+  return (
+    <EventDataSection type="attachments" title={title}>
+      {crashFileStripped && (
+        <EventAttachmentsCrashReportsNotice
+          orgSlug={organization.slug}
+          projectSlug={projectSlug}
+          groupId={event.groupID!}
+          location={location}
+        />
+      )}
+
+      {attachments.length > 0 && (
+        <StyledPanelTable
+          headers={[
+            <Name key="name">{t('File Name')}</Name>,
+            <Size key="size">{t('Size')}</Size>,
+            t('Actions'),
+          ]}
+        >
+          {attachments.map(attachment => (
+            <Fragment key={attachment.id}>
+              <Name>{attachment.name}</Name>
+              <Size>
+                <FileSize bytes={attachment.size} />
+              </Size>
+              <AttachmentUrl
+                projectSlug={projectSlug}
+                eventId={event.id}
+                attachment={attachment}
+              >
+                {url => (
+                  <div>
+                    <EventAttachmentActions
+                      url={url}
+                      onDelete={onDeleteAttachment}
+                      onPreview={_attachmentId => togglePreview(attachment)}
+                      withPreviewButton
+                      previewIsOpen={attachmentPreviewIsOpen(
+                        attachmentPreviews,
+                        attachment
+                      )}
+                      hasPreview={hasInlineAttachmentRenderer(attachment)}
+                      attachmentId={attachment.id}
+                    />
+                  </div>
                 )}
-              </Fragment>
-            ))}
-          </StyledPanelTable>
-        )}
-      </EventDataSection>
-    );
-  }
-}
-
-export default EventAttachments;
+              </AttachmentUrl>
+              <InlineEventAttachment
+                {...{attachment, attachmentPreviews, event, projectSlug}}
+              />
+              {/* XXX: hack to deal with table grid borders */}
+              {lastAttachmentPreviewed && (
+                <Fragment>
+                  <div style={{display: 'none'}} />
+                  <div style={{display: 'none'}} />
+                </Fragment>
+              )}
+            </Fragment>
+          ))}
+        </StyledPanelTable>
+      )}
+    </EventDataSection>
+  );
+};
 
 const StyledPanelTable = styled(PanelTable)`
   grid-template-columns: 1fr auto auto;

+ 0 - 2
static/app/components/events/eventEntries.tsx

@@ -189,9 +189,7 @@ const EventEntries = ({
       {!isShare && hasEventAttachmentsFeature && (
         <EventAttachments
           event={event}
-          orgId={orgSlug}
           projectSlug={projectSlug}
-          location={location}
           attachments={attachments}
           onDeleteAttachment={handleDeleteAttachment}
         />