Skip to content

feat(screenshots): Update render logic #93211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import type {IssueAttachment} from 'sentry/types/group';

export const getInlineAttachmentRenderer = (
attachment: IssueAttachment
): typeof ImageViewer | typeof LogFileViewer | typeof RRWebJsonViewer | undefined => {
):
| typeof ImageViewer
| typeof LogFileViewer
| typeof RRWebJsonViewer
| typeof WebMViewer
| undefined => {
switch (attachment.mimetype) {
case 'text/css':
case 'text/csv':
Expand Down
11 changes: 8 additions & 3 deletions static/app/components/events/attachmentViewers/webmViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import PanelItem from 'sentry/components/panels/panelItem';
import {t} from 'sentry/locale';

interface WebMViewerProps
extends Pick<ViewerProps, 'attachment' | 'eventId' | 'orgSlug' | 'projectSlug'> {}
extends Pick<ViewerProps, 'attachment' | 'eventId' | 'orgSlug' | 'projectSlug'>,
Partial<Pick<HTMLVideoElement, 'controls'>> {
onCanPlay?: React.ReactEventHandler<HTMLVideoElement>;
}

export function WebMViewer(props: WebMViewerProps) {
export function WebMViewer({controls = true, onCanPlay, ...props}: WebMViewerProps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity: Why do we limit this to WebM? Couldn't we try to display any video type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must rethink these components and refactor them. Agree that we could have something like VideoViewer or something.

return (
<PanelItem>
<video
controls
onCanPlay={onCanPlay}
controls={controls}
css={css`
width: 100%;
max-width: 100%;
`}
>
Expand Down
7 changes: 2 additions & 5 deletions static/app/components/events/eventTagsAndScreenshot/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ export function EventTagsAndScreenshot({projectSlug, event, isShare = false}: Pr
},
{enabled: !isShare}
);
const screenshots =
attachments?.filter(
({name}) =>
name.includes('screenshot') && (name.endsWith('.jpg') || name.endsWith('.png'))
) ?? [];

const screenshots = attachments?.filter(({name}) => name.includes('screenshot')) ?? [];

if (!tags.length && (isShare || !screenshots.length)) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {openConfirmModal} from 'sentry/components/confirm';
import {Button} from 'sentry/components/core/button';
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
import {DropdownMenu} from 'sentry/components/dropdownMenu';
import {getInlineAttachmentRenderer} from 'sentry/components/events/attachmentViewers/previewAttachmentTypes';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import Panel from 'sentry/components/panels/panel';
import PanelBody from 'sentry/components/panels/panelBody';
Expand All @@ -22,8 +23,6 @@ import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {trackAnalytics} from 'sentry/utils/analytics';

import ImageVisualization from './imageVisualization';

type Props = {
eventId: Event['id'];
onDelete: (attachmentId: EventAttachment['id']) => void;
Expand All @@ -38,6 +37,14 @@ type Props = {
onlyRenderScreenshot?: boolean;
};

const loadingMimeTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'video/webm',
];

function Screenshot({
eventId,
organization,
Expand All @@ -51,8 +58,9 @@ function Screenshot({
onDelete,
openVisualizationModal,
}: Props) {
const orgSlug = organization.slug;
const [loadingImage, setLoadingImage] = useState(true);
const [loadingImage, setLoadingImage] = useState(
loadingMimeTypes.includes(screenshot.mimetype)
);
const {hasRole} = useRole({role: 'attachmentsRole'});

function handleDelete(screenshotAttachmentId: string) {
Expand All @@ -62,8 +70,14 @@ function Screenshot({
onDelete(screenshotAttachmentId);
}

if (!hasRole) {
return null;
}

function renderContent(screenshotAttachment: EventAttachment) {
const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${eventId}/attachments/${screenshotAttachment.id}/`;
const AttachmentComponent = getInlineAttachmentRenderer(screenshotAttachment)!;

const downloadUrl = `/api/0/projects/${organization.slug}/${projectSlug}/events/${eventId}/attachments/${screenshot.id}/`;

return (
<Fragment>
Expand Down Expand Up @@ -95,20 +109,22 @@ function Screenshot({
<LoadingIndicator mini />
</StyledLoadingIndicator>
)}
<StyledImageWrapper
<AttachmentComponentWrapper
onClick={() =>
openVisualizationModal(screenshotAttachment, `${downloadUrl}?download=1`)
openVisualizationModal(screenshot, `${downloadUrl}?download=1`)
}
>
<StyledImageVisualization
attachment={screenshotAttachment}
orgSlug={orgSlug}
<AttachmentComponent
orgSlug={organization.slug}
projectSlug={projectSlug}
eventId={eventId}
attachment={screenshot}
onLoad={() => setLoadingImage(false)}
onError={() => setLoadingImage(false)}
controls={false}
onCanPlay={() => setLoadingImage(false)}
/>
</StyledImageWrapper>
</AttachmentComponentWrapper>
</StyledPanelBody>
{!onlyRenderScreenshot && (
<StyledPanelFooter>
Expand Down Expand Up @@ -240,14 +256,14 @@ const StyledLoadingIndicator = styled('div')`
height: 100%;
`;

const StyledImageWrapper = styled('div')`
const AttachmentComponentWrapper = styled('div')`
:hover {
cursor: pointer;
}
`;

const StyledImageVisualization = styled(ImageVisualization)`
width: 100%;
z-index: 1;
border: 0;
& > * {
width: 100%;
z-index: 1;
border: 0;
padding: 0 !important;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {Button} from 'sentry/components/core/button';
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
import {LinkButton} from 'sentry/components/core/button/linkButton';
import {DateTime} from 'sentry/components/dateTime';
import {getInlineAttachmentRenderer} from 'sentry/components/events/attachmentViewers/previewAttachmentTypes';
import {KeyValueData} from 'sentry/components/keyValueData';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
Expand All @@ -20,7 +21,6 @@ import {formatBytesBase2} from 'sentry/utils/bytes/formatBytesBase2';
import {useHotkeys} from 'sentry/utils/useHotkeys';
import useOrganization from 'sentry/utils/useOrganization';

import ImageVisualization from './imageVisualization';
import ScreenshotPagination from './screenshotPagination';

interface ScreenshotModalProps extends ModalRenderProps {
Expand Down Expand Up @@ -58,9 +58,7 @@ export default function ScreenshotModal({
}: ScreenshotModalProps) {
const organization = useOrganization();

const screenshots = attachments.filter(
({name, mimetype}) => name.includes('screenshot') && mimetype.startsWith('image')
);
const screenshots = attachments.filter(({name}) => name.includes('screenshot'));

const [currentEventAttachment, setCurrentAttachment] =
useState<EventAttachment>(eventAttachment);
Expand Down Expand Up @@ -108,6 +106,8 @@ export default function ScreenshotModal({
};
}

const AttachmentComponent = getInlineAttachmentRenderer(currentEventAttachment)!;

return (
<Fragment>
<Header closeButton>
Expand All @@ -116,12 +116,14 @@ export default function ScreenshotModal({
<Body>
<Flex column gap={space(1.5)}>
{defined(paginationProps) && <ScreenshotPagination {...paginationProps} />}
<StyledImageVisualization
attachment={currentEventAttachment}
orgSlug={organization.slug}
projectSlug={projectSlug}
eventId={currentEventAttachment.event_id}
/>
<AttachmentComponentWrapper>
<AttachmentComponent
attachment={currentEventAttachment}
orgSlug={organization.slug}
projectSlug={projectSlug}
eventId={currentEventAttachment.event_id}
/>
</AttachmentComponentWrapper>
<KeyValueData.Card
title={currentEventAttachment.name}
contentItems={[
Expand Down Expand Up @@ -179,10 +181,12 @@ export default function ScreenshotModal({
);
}

const StyledImageVisualization = styled(ImageVisualization)`
border-bottom: 0;
img {
const AttachmentComponentWrapper = styled('div')`
& > * {
padding: 0;
border: none;
max-height: calc(100vh - 300px);
box-sizing: border-box;
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,6 @@ import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSectio
import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';

const SCREENSHOT_NAMES = [
'screenshot.jpg',
'screenshot.png',
'screenshot-1.jpg',
'screenshot-1.png',
'screenshot-2.jpg',
'screenshot-2.png',
];

interface ScreenshotDataSectionProps {
event: Event;
projectSlug: Project['slug'];
Expand All @@ -57,8 +48,7 @@ export function ScreenshotDataSection({
{enabled: !isShare}
);
const {mutate: deleteAttachment} = useDeleteEventAttachmentOptimistic();
const screenshots =
attachments?.filter(({name}) => SCREENSHOT_NAMES.includes(name)) ?? [];
const screenshots = attachments?.filter(({name}) => name.includes('screenshot')) ?? [];

const [screenshotInFocus, setScreenshotInFocus] = useState<number>(0);

Expand Down
Loading