1
- import { useState } from 'react' ;
1
+ import { useRef , useState } from 'react' ;
2
2
import styled from '@emotion/styled' ;
3
3
4
4
import FeatureBadge from 'sentry/components/badge/featureBadge' ;
5
5
import { Button } from 'sentry/components/button' ;
6
+ import { Chevron } from 'sentry/components/chevron' ;
7
+ import useDrawer from 'sentry/components/globalDrawer' ;
6
8
import { GroupSummary } from 'sentry/components/group/groupSummary' ;
9
+ import Placeholder from 'sentry/components/placeholder' ;
7
10
import { IconMegaphone } from 'sentry/icons' ;
8
11
import { t , tct } from 'sentry/locale' ;
9
12
import { space } from 'sentry/styles/space' ;
@@ -16,10 +19,9 @@ import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
16
19
import { FoldSection } from 'sentry/views/issueDetails/streamline/foldSection' ;
17
20
import { useAiConfig } from 'sentry/views/issueDetails/streamline/hooks/useAiConfig' ;
18
21
import Resources from 'sentry/views/issueDetails/streamline/sidebar/resources' ;
22
+ import { SolutionsHubDrawer } from 'sentry/views/issueDetails/streamline/sidebar/solutionsHubDrawer' ;
19
23
import { useHasStreamlinedUI } from 'sentry/views/issueDetails/utils' ;
20
24
21
- import { SolutionsSectionCtaButton } from './solutionsSectionCtaButton' ;
22
-
23
25
function SolutionsHubFeedbackButton ( { hidden} : { hidden : boolean } ) {
24
26
const openFeedbackForm = useFeedbackForm ( ) ;
25
27
if ( hidden ) {
@@ -55,10 +57,60 @@ export default function SolutionsSection({
55
57
const hasStreamlinedUI = useHasStreamlinedUI ( ) ;
56
58
// We don't use this on the streamlined UI, since the section folds.
57
59
const [ isExpanded , setIsExpanded ] = useState ( false ) ;
60
+ const openButtonRef = useRef < HTMLButtonElement > ( null ) ;
61
+ const { openDrawer} = useDrawer ( ) ;
62
+
63
+ const openSolutionsDrawer = ( ) => {
64
+ if ( ! event ) {
65
+ return ;
66
+ }
67
+ openDrawer (
68
+ ( ) => < SolutionsHubDrawer group = { group } project = { project } event = { event } /> ,
69
+ {
70
+ ariaLabel : t ( 'Solutions drawer' ) ,
71
+ // We prevent a click on the Open/Close Autofix button from closing the drawer so that
72
+ // we don't reopen it immediately, and instead let the button handle this itself.
73
+ shouldCloseOnInteractOutside : element => {
74
+ const viewAllButton = openButtonRef . current ;
75
+ if (
76
+ viewAllButton ?. contains ( element ) ||
77
+ document . getElementById ( 'sentry-feedback' ) ?. contains ( element ) ||
78
+ document . getElementById ( 'autofix-rethink-input' ) ?. contains ( element ) ||
79
+ document . getElementById ( 'autofix-output-stream' ) ?. contains ( element ) ||
80
+ document . getElementById ( 'autofix-write-access-modal' ) ?. contains ( element ) ||
81
+ element . closest ( '[data-overlay="true"]' )
82
+ ) {
83
+ return false ;
84
+ }
85
+ return true ;
86
+ } ,
87
+ transitionProps : { stiffness : 1000 } ,
88
+ }
89
+ ) ;
90
+ } ;
58
91
59
92
const aiConfig = useAiConfig ( group , event , project ) ;
60
93
const issueTypeConfig = getConfigForIssueType ( group , project ) ;
61
94
95
+ const showCtaButton =
96
+ aiConfig . needsGenAIConsent ||
97
+ aiConfig . hasAutofix ||
98
+ ( aiConfig . hasSummary && aiConfig . hasResources ) ;
99
+ const isButtonLoading = aiConfig . isAutofixSetupLoading ;
100
+
101
+ const getButtonText = ( ) => {
102
+ if ( aiConfig . needsGenAIConsent ) {
103
+ return t ( 'Set up Sentry AI' ) ;
104
+ }
105
+ if ( ! aiConfig . hasAutofix ) {
106
+ return t ( 'Open Resources' ) ;
107
+ }
108
+ if ( aiConfig . needsAutofixSetup ) {
109
+ return t ( 'Set up Autofix' ) ;
110
+ }
111
+ return aiConfig . hasResources ? t ( 'Open Resources & Autofix' ) : t ( 'Open Autofix' ) ;
112
+ } ;
113
+
62
114
const renderContent = ( ) => {
63
115
if ( aiConfig . needsGenAIConsent ) {
64
116
return (
@@ -124,15 +176,24 @@ export default function SolutionsSection({
124
176
>
125
177
< SolutionsSectionContainer >
126
178
{ renderContent ( ) }
127
- { event && (
128
- < SolutionsSectionCtaButton
129
- aiConfig = { aiConfig }
130
- event = { event }
131
- group = { group }
132
- project = { project }
133
- hasStreamlinedUI = { hasStreamlinedUI }
134
- />
135
- ) }
179
+ { isButtonLoading ? (
180
+ < ButtonPlaceholder />
181
+ ) : showCtaButton ? (
182
+ < StyledButton
183
+ ref = { openButtonRef }
184
+ onClick = { ( ) => openSolutionsDrawer ( ) }
185
+ analyticsEventKey = "issue_details.solutions_hub_opened"
186
+ analyticsEventName = "Issue Details: Solutions Hub Opened"
187
+ analyticsParams = { {
188
+ has_streamlined_ui : hasStreamlinedUI ,
189
+ } }
190
+ >
191
+ { getButtonText ( ) }
192
+ < ChevronContainer >
193
+ < Chevron direction = "right" size = "large" />
194
+ </ ChevronContainer >
195
+ </ StyledButton >
196
+ ) : null }
136
197
</ SolutionsSectionContainer >
137
198
</ SidebarFoldSection >
138
199
) ;
@@ -193,9 +254,30 @@ const ExpandButton = styled(Button)`
193
254
}
194
255
` ;
195
256
257
+ const StyledButton = styled ( Button ) `
258
+ margin-top: ${ space ( 1 ) } ;
259
+ width: 100%;
260
+ background: ${ p => p . theme . background }
261
+ linear-gradient(to right, ${ p => p . theme . background } , ${ p => p . theme . pink400 } 20);
262
+ color: ${ p => p . theme . pink400 } ;
263
+ ` ;
264
+
265
+ const ChevronContainer = styled ( 'div' ) `
266
+ margin-left: ${ space ( 0.5 ) } ;
267
+ height: 16px;
268
+ width: 16px;
269
+ ` ;
270
+
196
271
const HeaderContainer = styled ( 'div' ) `
197
272
font-size: ${ p => p . theme . fontSizeMedium } ;
198
273
display: flex;
199
274
align-items: center;
200
275
gap: ${ space ( 0.25 ) } ;
201
276
` ;
277
+
278
+ const ButtonPlaceholder = styled ( Placeholder ) `
279
+ width: 100%;
280
+ height: 38px;
281
+ border-radius: ${ p => p . theme . borderRadius } ;
282
+ margin-top: ${ space ( 1 ) } ;
283
+ ` ;
0 commit comments