Skip to content

Commit b53da03

Browse files
authored
feat(feedback): Split feedback tags into tags+context with collapsable sections (#93397)
**1. Overall changes** I moved around the "Seen By" stuff to save a little vertical space; it used to be directly below the message, now it's in the header All Sections are collapseable now, saved into local storage <img width="892" alt="SCR-20250611-ozjr" src="https://github.com/user-attachments/assets/340bd0c5-a55c-4483-886b-fa3dd36b6f51" /> **2. Tags & Context changes** I split up Tags & Context, and re-used the components from Issue Details. There's no way to split up Custom tags, but everything will be shown. Fixes REPLAY-67 <img width="875" alt="SCR-20250611-ozqr" src="https://github.com/user-attachments/assets/e41ed5ad-e445-44dc-9aa2-7a00ad6ce429" />
1 parent dba5bc9 commit b53da03

File tree

11 files changed

+219
-236
lines changed

11 files changed

+219
-236
lines changed

static/app/components/events/eventTags/eventTagsTreeRow.tsx

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -161,24 +161,22 @@ function EventTagsTreeRowDropdown({
161161
project,
162162
});
163163
const isIssueDetailsRoute = location.pathname.includes(`issues/${event.groupID}/`);
164+
const isFeedback = Boolean(event.contexts.feedback);
164165

165166
const items: MenuItemProps[] = [
166-
...(isIssueDetailsRoute
167-
? [
168-
{
169-
key: 'tag-details',
170-
label: t('Tag breakdown'),
171-
to: {
172-
pathname: `/organizations/${organization.slug}/issues/${event.groupID}/${TabPaths[Tab.DISTRIBUTIONS]}${encodeURIComponent(originalTag.key)}/`,
173-
query: location.query,
174-
},
175-
},
176-
]
177-
: []),
167+
{
168+
key: 'tag-details',
169+
label: t('Tag breakdown'),
170+
hidden: !isIssueDetailsRoute,
171+
to: {
172+
pathname: `/organizations/${organization.slug}/issues/${event.groupID}/${TabPaths[Tab.DISTRIBUTIONS]}${encodeURIComponent(originalTag.key)}/`,
173+
query: location.query,
174+
},
175+
},
178176
{
179177
key: 'view-events',
180178
label: t('View other events with this tag value'),
181-
hidden: !event.groupID,
179+
hidden: !event.groupID || isFeedback,
182180
to: {
183181
pathname: `/organizations/${organization.slug}/issues/${event.groupID}/events/`,
184182
query,
@@ -187,17 +185,25 @@ function EventTagsTreeRowDropdown({
187185
{
188186
key: 'view-issues',
189187
label: t('Search issues with this tag value'),
188+
hidden: isFeedback,
190189
to: {
191190
pathname: `/organizations/${organization.slug}/issues/`,
192191
query,
193192
},
194193
},
195-
];
196-
197-
if (hasExploreEnabled) {
198-
items.push({
194+
{
195+
key: 'view-feedback',
196+
label: t('Search feedbacks with this tag value'),
197+
hidden: !isFeedback,
198+
to: {
199+
pathname: `/organizations/${organization.slug}/feedback/`,
200+
query,
201+
},
202+
},
203+
{
199204
key: 'view-traces',
200205
label: t('Find more samples with this value'),
206+
hidden: !hasExploreEnabled || isFeedback,
201207
to: getSearchInExploreTarget(
202208
organization,
203209
location,
@@ -215,10 +221,7 @@ function EventTagsTreeRowDropdown({
215221
'drawer'
216222
);
217223
},
218-
});
219-
}
220-
221-
items.push(
224+
},
222225
{
223226
key: 'copy-value',
224227
label: t('Copy tag value to clipboard'),
@@ -227,7 +230,7 @@ function EventTagsTreeRowDropdown({
227230
{
228231
key: 'add-to-highlights',
229232
label: t('Add to event highlights'),
230-
hidden: hideAddHighlightsOption || !isProjectAdmin,
233+
hidden: hideAddHighlightsOption || !isProjectAdmin || isFeedback,
231234
onAction: () => {
232235
saveTag({
233236
highlightTags: [...(project?.highlightTags ?? []), originalTag.key],
@@ -284,8 +287,8 @@ function EventTagsTreeRowDropdown({
284287
onAction: () => {
285288
openNavigateToExternalLinkModal({linkText: content.value});
286289
},
287-
}
288-
);
290+
},
291+
];
289292

290293
return (
291294
<TreeValueDropdown

static/app/components/feedback/feedbackItem/feedbackItem.tsx

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
import type {ReactNode} from 'react';
21
import {Fragment, useEffect, useRef} from 'react';
32
import {useTheme} from '@emotion/react';
43
import styled from '@emotion/styled';
54

65
import {openNavigateToExternalLinkModal} from 'sentry/actionCreators/modal';
76
import AnalyticsArea from 'sentry/components/analyticsArea';
87
import ErrorBoundary from 'sentry/components/errorBoundary';
8+
import {getOrderedContextItems} from 'sentry/components/events/contexts';
9+
import ContextCard from 'sentry/components/events/contexts/contextCard';
10+
import EventTagsTree from 'sentry/components/events/eventTags/eventTagsTree';
911
import CrashReportSection from 'sentry/components/feedback/feedbackItem/crashReportSection';
1012
import FeedbackActivitySection from 'sentry/components/feedback/feedbackItem/feedbackActivitySection';
1113
import FeedbackItemHeader from 'sentry/components/feedback/feedbackItem/feedbackItemHeader';
12-
import Section from 'sentry/components/feedback/feedbackItem/feedbackItemSection';
14+
import FeedbackItemSection from 'sentry/components/feedback/feedbackItem/feedbackItemSection';
1315
import FeedbackReplay from 'sentry/components/feedback/feedbackItem/feedbackReplay';
1416
import MessageSection from 'sentry/components/feedback/feedbackItem/messageSection';
15-
import TagsSection from 'sentry/components/feedback/feedbackItem/tagsSection';
1617
import TraceDataSection from 'sentry/components/feedback/feedbackItem/traceDataSection';
18+
import {KeyValueData} from 'sentry/components/keyValueData';
1719
import PanelItem from 'sentry/components/panels/panelItem';
1820
import QuestionTooltip from 'sentry/components/questionTooltip';
1921
import TextCopyInput from 'sentry/components/textCopyInput';
@@ -22,16 +24,16 @@ import {t} from 'sentry/locale';
2224
import {space} from 'sentry/styles/space';
2325
import type {Event} from 'sentry/types/event';
2426
import type {Group} from 'sentry/types/group';
27+
import type {Project} from 'sentry/types/project';
2528
import type {FeedbackIssue} from 'sentry/utils/feedback/types';
2629
import useOrganization from 'sentry/utils/useOrganization';
2730

2831
interface Props {
2932
eventData: Event | undefined;
3033
feedbackItem: FeedbackIssue;
31-
tags: Record<string, string | ReactNode>;
3234
}
3335

34-
export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
36+
export default function FeedbackItem({feedbackItem, eventData}: Props) {
3537
const organization = useOrganization();
3638
const url =
3739
eventData?.contexts?.feedback?.url ??
@@ -59,12 +61,17 @@ export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
5961
<AnalyticsArea name="details">
6062
<FeedbackItemHeader eventData={eventData} feedbackItem={feedbackItem} />
6163
<OverflowPanelItem ref={overflowRef}>
62-
<Section>
64+
<FeedbackItemSection sectionKey="message">
6365
<MessageSection eventData={eventData} feedbackItem={feedbackItem} />
64-
</Section>
66+
</FeedbackItemSection>
6567

6668
{!crashReportId || (crashReportId && url) ? (
67-
<Section icon={<IconLink size="xs" />} title={t('URL')}>
69+
<FeedbackItemSection
70+
collapsible
71+
icon={<IconLink size="xs" />}
72+
sectionKey="url"
73+
title={t('URL')}
74+
>
6875
<TextCopyInput
6976
style={urlIsLink ? {color: theme.blue400} : undefined}
7077
onClick={
@@ -78,19 +85,24 @@ export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
7885
>
7986
{displayUrl}
8087
</TextCopyInput>
81-
</Section>
88+
</FeedbackItemSection>
8289
) : null}
8390

8491
{crashReportId && feedbackItem.project ? (
85-
<Section icon={<IconFire size="xs" />} title={t('Linked Error')}>
92+
<FeedbackItemSection
93+
collapsible
94+
icon={<IconFire size="xs" />}
95+
sectionKey="crash-report"
96+
title={t('Linked Error')}
97+
>
8698
<ErrorBoundary mini>
8799
<CrashReportSection
88100
organization={organization}
89101
crashReportId={crashReportId}
90102
projectSlug={feedbackItem.project.slug}
91103
/>
92104
</ErrorBoundary>
93-
</Section>
105+
</FeedbackItemSection>
94106
) : null}
95107

96108
<FeedbackReplay
@@ -105,13 +117,41 @@ export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
105117
</ErrorBoundary>
106118
) : null}
107119

108-
<Section icon={<IconTag size="xs" />} title={t('Tags')}>
109-
<TagsSection tags={tags} />
110-
</Section>
120+
{eventData && feedbackItem.project ? (
121+
<FeedbackItemSection
122+
collapsible
123+
icon={<IconTag size="xs" />}
124+
sectionKey="tags"
125+
title={t('Tags')}
126+
>
127+
<EventTagsTree
128+
event={eventData}
129+
projectSlug={feedbackItem.project.slug}
130+
tags={eventData.tags}
131+
/>
132+
</FeedbackItemSection>
133+
) : null}
134+
135+
{eventData ? (
136+
<FeedbackItemSection
137+
collapsible
138+
icon={<IconTag size="xs" />}
139+
sectionKey="context"
140+
title={t('Context')}
141+
>
142+
<FeedbackItemContexts
143+
feedbackItem={feedbackItem}
144+
eventData={eventData}
145+
project={feedbackItem.project}
146+
/>
147+
</FeedbackItemSection>
148+
) : null}
111149

112150
{feedbackItem.project ? (
113-
<Section
151+
<FeedbackItemSection
152+
collapsible
114153
icon={<IconChat size="xs" />}
154+
sectionKey="activity"
115155
title={
116156
<Fragment>
117157
{t('Internal Activity')}
@@ -125,14 +165,48 @@ export default function FeedbackItem({feedbackItem, eventData, tags}: Props) {
125165
}
126166
>
127167
<FeedbackActivitySection feedbackItem={feedbackItem as unknown as Group} />
128-
</Section>
168+
</FeedbackItemSection>
129169
) : null}
130170
</OverflowPanelItem>
131171
</AnalyticsArea>
132172
</Fragment>
133173
);
134174
}
135175

176+
function FeedbackItemContexts({
177+
eventData,
178+
feedbackItem,
179+
project,
180+
}: {
181+
eventData: Event;
182+
feedbackItem: FeedbackIssue;
183+
project: undefined | Project;
184+
}) {
185+
const cards = getOrderedContextItems(eventData).map(
186+
({alias, type, value: contextValue}) => (
187+
<ContextCard
188+
key={alias}
189+
type={type}
190+
alias={alias}
191+
value={contextValue}
192+
event={eventData}
193+
group={feedbackItem as unknown as Group}
194+
project={project}
195+
/>
196+
)
197+
);
198+
199+
if (!cards.length) {
200+
return null;
201+
}
202+
203+
return (
204+
<ErrorBoundary mini message={t('There was a problem loading event context.')}>
205+
<KeyValueData.Container>{cards}</KeyValueData.Container>
206+
</ErrorBoundary>
207+
);
208+
}
209+
136210
// 0 padding-bottom because <ActivitySection> has space(2) built-in.
137211
const OverflowPanelItem = styled(PanelItem)`
138212
overflow: auto;

static/app/components/feedback/feedbackItem/feedbackItemHeader.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {Flex} from 'sentry/components/core/layout';
55
import ErrorBoundary from 'sentry/components/errorBoundary';
66
import FeedbackActions from 'sentry/components/feedback/feedbackItem/feedbackActions';
77
import FeedbackShortId from 'sentry/components/feedback/feedbackItem/feedbackShortId';
8+
import FeedbackViewers from 'sentry/components/feedback/feedbackItem/feedbackViewers';
89
import {StreamlinedExternalIssueList} from 'sentry/components/group/externalIssuesList/streamlinedExternalIssueList';
10+
import {t} from 'sentry/locale';
911
import {space} from 'sentry/styles/space';
1012
import type {Event} from 'sentry/types/event';
1113
import type {Group} from 'sentry/types/group';
@@ -46,12 +48,20 @@ export default function FeedbackItemHeader({eventData, feedbackItem}: Props) {
4648

4749
{eventData && feedbackItem.project ? (
4850
<ErrorBoundary mini>
49-
<Flex wrap="wrap" justify="flex-start" align="center" gap={space(1)}>
51+
<Flex wrap="wrap" justify="space-between" align="center" gap={space(1)}>
5052
<StreamlinedExternalIssueList
5153
group={feedbackItem as unknown as Group}
5254
project={feedbackItem.project}
5355
event={eventData}
5456
/>
57+
{feedbackItem.seenBy.length ? (
58+
<Flex justify="flex-end">
59+
<Flex gap={space(1)} align="center">
60+
<SeenBy>{t('Seen by')}</SeenBy>
61+
<FeedbackViewers feedbackItem={feedbackItem} />
62+
</Flex>
63+
</Flex>
64+
) : null}
5565
</Flex>
5666
</ErrorBoundary>
5767
) : null}
@@ -66,3 +76,8 @@ const VerticalSpacing = styled('div')`
6676
padding: ${space(1)} ${space(2)};
6777
border-bottom: 1px solid ${p => p.theme.innerBorder};
6878
`;
79+
80+
const SeenBy = styled('span')`
81+
color: ${p => p.theme.subText};
82+
font-size: ${p => p.theme.fontSizeRelativeSmall};
83+
`;

static/app/components/feedback/feedbackItem/feedbackItemLoader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import useOrganization from 'sentry/utils/useOrganization';
1616
export default function FeedbackItemLoader() {
1717
const organization = useOrganization();
1818
const feedbackId = useCurrentFeedbackId();
19-
const {issueResult, issueData, tags, eventData} = useFetchFeedbackData({feedbackId});
19+
const {issueResult, issueData, eventData} = useFetchFeedbackData({feedbackId});
2020

2121
const projectSlug = useCurrentFeedbackProject();
2222
useSentryAppComponentsData({projectId: projectSlug});
@@ -50,7 +50,7 @@ export default function FeedbackItemLoader() {
5050
<FeedbackErrorDetails error={t('Unable to load feedback')} />
5151
)}
5252
>
53-
<FeedbackItem eventData={eventData} feedbackItem={issueData} tags={tags} />
53+
<FeedbackItem eventData={eventData} feedbackItem={issueData} />
5454
</ErrorBoundary>
5555
) : (
5656
<FeedbackEmptyDetails />

0 commit comments

Comments
 (0)