Files
2024-02-15 17:01:50 +00:00

147 lines
4.8 KiB
TypeScript

import {Fragment, useEffect, useRef} from 'react';
import styled from '@emotion/styled';
import ErrorBoundary from 'sentry/components/errorBoundary';
import CrashReportSection from 'sentry/components/feedback/feedbackItem/crashReportSection';
import FeedbackActivitySection from 'sentry/components/feedback/feedbackItem/feedbackActivitySection';
import FeedbackItemHeader from 'sentry/components/feedback/feedbackItem/feedbackItemHeader';
import Section from 'sentry/components/feedback/feedbackItem/feedbackItemSection';
import FeedbackReplay from 'sentry/components/feedback/feedbackItem/feedbackReplay';
import FeedbackViewers from 'sentry/components/feedback/feedbackItem/feedbackViewers';
import TagsSection from 'sentry/components/feedback/feedbackItem/tagsSection';
import PanelItem from 'sentry/components/panels/panelItem';
import {Flex} from 'sentry/components/profiling/flex';
import QuestionTooltip from 'sentry/components/questionTooltip';
import TextCopyInput from 'sentry/components/textCopyInput';
import {IconChat, IconFire, IconLink, IconPlay, IconTag} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Event} from 'sentry/types';
import type {FeedbackIssue} from 'sentry/utils/feedback/types';
import useOrganization from 'sentry/utils/useOrganization';
interface Props {
eventData: Event | undefined;
feedbackItem: FeedbackIssue;
tags: Record<string, string>;
}
export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
const organization = useOrganization();
const url = eventData?.tags.find(tag => tag.key === 'url');
const crashReportId = eventData?.contexts?.feedback?.associated_event_id;
const overflowRef = useRef<HTMLDivElement>(null);
useEffect(() => {
overflowRef.current?.scrollTo({top: 0});
}, [feedbackItem.id]);
return (
<Fragment>
<FeedbackItemHeader eventData={eventData} feedbackItem={feedbackItem} />
<OverflowPanelItem ref={overflowRef}>
<Section
title={t('Description')}
contentRight={
<Flex gap={space(1)} align="center">
<SmallTitle>{t('Viewers')}</SmallTitle>
<FeedbackViewers feedbackItem={feedbackItem} />
</Flex>
}
>
<Blockquote>
<pre>{feedbackItem.metadata.message}</pre>
</Blockquote>
</Section>
{!crashReportId || (crashReportId && url) ? (
<Section icon={<IconLink size="xs" />} title={t('URL')}>
<TextCopyInput size="sm">
{eventData?.tags ? (url ? url.value : t('URL not found')) : ''}
</TextCopyInput>
</Section>
) : null}
{crashReportId && (
<Section icon={<IconFire size="xs" />} title={t('Linked Error')}>
<ErrorBoundary mini>
<CrashReportSection
organization={organization}
crashReportId={crashReportId}
projectSlug={feedbackItem.project.slug}
/>
</ErrorBoundary>
</Section>
)}
<Section icon={<IconPlay size="xs" />} title={t('Linked Replay')}>
<FeedbackReplay
eventData={eventData}
feedbackItem={feedbackItem}
organization={organization}
/>
</Section>
<Section icon={<IconTag size="xs" />} title={t('Tags')}>
<TagsSection tags={tags} />
</Section>
<Section
icon={<IconChat size="xs" />}
title={
<Fragment>
{t('Activity')}
<QuestionTooltip
size="xs"
title={t(
'Use this section to post comments that are visible only to your organization. It will also automatically update when someone resolves or assigns the feedback.'
)}
/>
</Fragment>
}
>
<FeedbackActivitySection feedbackItem={feedbackItem} />
</Section>
</OverflowPanelItem>
</Fragment>
);
}
// 0 padding-bottom because <ActivitySection> has space(2) built-in.
const OverflowPanelItem = styled(PanelItem)`
overflow: scroll;
flex-direction: column;
flex-grow: 1;
gap: ${space(4)};
padding: ${space(2)} ${space(3)} 0 ${space(3)};
`;
const SmallTitle = styled('span')`
font-size: ${p => p.theme.fontSizeRelativeSmall};
`;
const Blockquote = styled('blockquote')`
margin: 0 ${space(4)};
position: relative;
&::after {
position: absolute;
border: 1px solid ${p => p.theme.purple300};
bottom: 0;
content: '';
left: -${space(1)};
top: 0;
}
& > pre {
margin: 0;
background: none;
font-family: inherit;
font-size: ${p => p.theme.fontSizeMedium};
line-height: 1.6;
padding: 0;
word-break: break-word;
}
`;