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