Skip to content

Commit 9635c0b

Browse files
committed
add copy as markdown to user feedback
1 parent 6ab39b7 commit 9635c0b

File tree

1 file changed

+112
-8
lines changed

1 file changed

+112
-8
lines changed

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

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type {CSSProperties} from 'react';
2-
import {Fragment} from 'react';
2+
import {Fragment, useCallback} from 'react';
33

44
import {Button} from 'sentry/components/core/button';
55
import {Flex} from 'sentry/components/core/layout';
@@ -8,11 +8,14 @@ import {DropdownMenu} from 'sentry/components/dropdownMenu';
88
import ErrorBoundary from 'sentry/components/errorBoundary';
99
import FeedbackAssignedTo from 'sentry/components/feedback/feedbackItem/feedbackAssignedTo';
1010
import useFeedbackActions from 'sentry/components/feedback/feedbackItem/useFeedbackActions';
11-
import {IconEllipsis} from 'sentry/icons';
11+
import {IconCopy, IconEllipsis} from 'sentry/icons';
1212
import {t} from 'sentry/locale';
1313
import type {Event} from 'sentry/types/event';
1414
import type {Group} from 'sentry/types/group';
15+
import {trackAnalytics} from 'sentry/utils/analytics';
1516
import type {FeedbackIssue} from 'sentry/utils/feedback/types';
17+
import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
18+
import useOrganization from 'sentry/utils/useOrganization';
1619

1720
interface Props {
1821
eventData: Event | undefined;
@@ -29,6 +32,53 @@ export default function FeedbackActions({
2932
size,
3033
style,
3134
}: Props) {
35+
const organization = useOrganization();
36+
const {copy} = useCopyToClipboard();
37+
const handleCopyToClipboard = useCallback(() => {
38+
const summary =
39+
feedbackItem.metadata.summary ??
40+
feedbackItem.metadata.title ??
41+
t('No summary provided');
42+
const message =
43+
feedbackItem.metadata.message ?? feedbackItem.metadata.value ?? t('No message');
44+
const culprit = eventData?.culprit?.trim();
45+
const viewNames = eventData?.contexts?.app?.view_names?.filter(Boolean);
46+
47+
const sourceLines = [];
48+
if (culprit) {
49+
sourceLines.push(`- ${culprit}`);
50+
}
51+
if (viewNames?.length) {
52+
sourceLines.push(t('- View names: %s', viewNames.join(', ')));
53+
}
54+
55+
const markdown = [
56+
'# User Feedback',
57+
'',
58+
`**Summary:** ${summary}`,
59+
'',
60+
'## Feedback Message',
61+
message,
62+
...(sourceLines.length
63+
? [
64+
'',
65+
'## Source (_where user was when feedback was sent_)',
66+
sourceLines.join('\n'),
67+
]
68+
: []),
69+
].join('\n');
70+
71+
trackAnalytics('feedback.copy-feedback-as-markdown', {
72+
organization,
73+
feedback_id: feedbackItem.id,
74+
project_slug: feedbackItem.project?.slug,
75+
});
76+
77+
copy(markdown, {
78+
successMessage: t('Copied feedback summary'),
79+
errorMessage: t('Failed to copy feedback'),
80+
});
81+
}, [copy, eventData, feedbackItem, organization]);
3282
if (!eventData) {
3383
return null;
3484
}
@@ -42,14 +92,35 @@ export default function FeedbackActions({
4292
/>
4393
</ErrorBoundary>
4494

45-
{size === 'large' ? <LargeWidth feedbackItem={feedbackItem} /> : null}
46-
{size === 'medium' ? <MediumWidth feedbackItem={feedbackItem} /> : null}
47-
{size === 'small' ? <SmallWidth feedbackItem={feedbackItem} /> : null}
95+
{size === 'large' ? (
96+
<LargeWidth
97+
feedbackItem={feedbackItem}
98+
onCopyToClipboard={handleCopyToClipboard}
99+
/>
100+
) : null}
101+
{size === 'medium' ? (
102+
<MediumWidth
103+
feedbackItem={feedbackItem}
104+
onCopyToClipboard={handleCopyToClipboard}
105+
/>
106+
) : null}
107+
{size === 'small' ? (
108+
<SmallWidth
109+
feedbackItem={feedbackItem}
110+
onCopyToClipboard={handleCopyToClipboard}
111+
/>
112+
) : null}
48113
</Flex>
49114
);
50115
}
51116

52-
function LargeWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
117+
function LargeWidth({
118+
feedbackItem,
119+
onCopyToClipboard,
120+
}: {
121+
feedbackItem: FeedbackIssue;
122+
onCopyToClipboard: () => void;
123+
}) {
53124
const {
54125
enableDelete,
55126
onDelete,
@@ -82,6 +153,15 @@ function LargeWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
82153
{hasSeen ? t('Mark Unread') : t('Mark Read')}
83154
</Button>
84155
</Tooltip>
156+
<Tooltip title={t('Copy feedback summary as markdown')}>
157+
<Button
158+
size="xs"
159+
priority="default"
160+
icon={<IconCopy />}
161+
onClick={onCopyToClipboard}
162+
aria-label={t('Copy feedback summary as markdown')}
163+
/>
164+
</Tooltip>
85165
<Tooltip
86166
disabled={enableDelete}
87167
title={t('You must be an admin to delete feedback')}
@@ -94,7 +174,13 @@ function LargeWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
94174
);
95175
}
96176

97-
function MediumWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
177+
function MediumWidth({
178+
feedbackItem,
179+
onCopyToClipboard,
180+
}: {
181+
feedbackItem: FeedbackIssue;
182+
onCopyToClipboard: () => void;
183+
}) {
98184
const {
99185
enableDelete,
100186
onDelete,
@@ -140,6 +226,12 @@ function MediumWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
140226
? undefined
141227
: t('You must be a member of the project'),
142228
},
229+
{
230+
key: 'copy',
231+
label: t('Copy as markdown'),
232+
onAction: onCopyToClipboard,
233+
tooltip: t('Copy feedback summary as markdown'),
234+
},
143235
{
144236
key: 'delete',
145237
priority: 'danger' as const,
@@ -156,7 +248,13 @@ function MediumWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
156248
);
157249
}
158250

159-
function SmallWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
251+
function SmallWidth({
252+
feedbackItem,
253+
onCopyToClipboard,
254+
}: {
255+
feedbackItem: FeedbackIssue;
256+
onCopyToClipboard: () => void;
257+
}) {
160258
const {
161259
enableDelete,
162260
onDelete,
@@ -189,6 +287,12 @@ function SmallWidth({feedbackItem}: {feedbackItem: FeedbackIssue}) {
189287
label: isSpam ? t('Move to Inbox') : t('Mark as Spam'),
190288
onAction: onSpamClick,
191289
},
290+
{
291+
key: 'copy',
292+
label: t('Copy as markdown'),
293+
onAction: onCopyToClipboard,
294+
tooltip: t('Copy feedback summary as markdown'),
295+
},
192296
{
193297
key: 'read',
194298
label: hasSeen ? t('Mark Unread') : t('Mark Read'),

0 commit comments

Comments
 (0)