@@ -12,6 +12,7 @@ import {t} from 'sentry/locale';
12
12
import { trackAnalytics } from 'sentry/utils/analytics' ;
13
13
import { isActiveSuperuser } from 'sentry/utils/isActiveSuperuser' ;
14
14
import { useFeedbackForm } from 'sentry/utils/useFeedbackForm' ;
15
+ import { useLocalStorageState } from 'sentry/utils/useLocalStorageState' ;
15
16
import useMutateUserOptions from 'sentry/utils/useMutateUserOptions' ;
16
17
import useOrganization from 'sentry/utils/useOrganization' ;
17
18
import { useUser } from 'sentry/utils/useUser' ;
@@ -25,17 +26,104 @@ import {
25
26
} from 'sentry/views/issueDetails/issueDetailsTourModal' ;
26
27
import { useHasStreamlinedUI } from 'sentry/views/issueDetails/utils' ;
27
28
29
+ /**
30
+ * This hook will cause the promotional modal to appear if:
31
+ * - All the steps have been registered
32
+ * - The tour has not been completed
33
+ * - The tour is not currently active
34
+ * - The streamline UI is enabled
35
+ * - The user's browser has not stored that they've seen the promo
36
+ *
37
+ * Returns a function that can be used to reset the modal.
38
+ */
39
+ export function useIssueDetailsPromoModal ( ) {
40
+ const organization = useOrganization ( ) ;
41
+ const hasStreamlinedUI = useHasStreamlinedUI ( ) ;
42
+ const { mutate : mutateAssistant } = useMutateAssistant ( ) ;
43
+ const {
44
+ startTour,
45
+ endTour,
46
+ currentStepId,
47
+ isRegistered : isTourRegistered ,
48
+ isCompleted : isTourCompleted ,
49
+ } = useIssueDetailsTour ( ) ;
50
+
51
+ const [ localTourState , setLocalTourState ] = useLocalStorageState (
52
+ ISSUE_DETAILS_TOUR_GUIDE_KEY ,
53
+ { hasSeen : false }
54
+ ) ;
55
+
56
+ const isPromoVisible =
57
+ isTourRegistered &&
58
+ ! isTourCompleted &&
59
+ currentStepId === null &&
60
+ hasStreamlinedUI &&
61
+ ! localTourState . hasSeen ;
62
+
63
+ const handleEndTour = useCallback ( ( ) => {
64
+ setLocalTourState ( { hasSeen : true } ) ;
65
+ mutateAssistant ( { guide : ISSUE_DETAILS_TOUR_GUIDE_KEY , status : 'dismissed' } ) ;
66
+ endTour ( ) ;
67
+ trackAnalytics ( 'issue_details.tour.skipped' , { organization} ) ;
68
+ } , [ mutateAssistant , organization , endTour , setLocalTourState ] ) ;
69
+
70
+ useEffect ( ( ) => {
71
+ if ( isPromoVisible ) {
72
+ openModal (
73
+ props => (
74
+ < IssueDetailsTourModal
75
+ handleDismissTour = { ( ) => {
76
+ handleEndTour ( ) ;
77
+ props . closeModal ( ) ;
78
+ } }
79
+ handleStartTour = { ( ) => {
80
+ props . closeModal ( ) ;
81
+ setLocalTourState ( { hasSeen : true } ) ;
82
+ startTour ( ) ;
83
+ trackAnalytics ( 'issue_details.tour.started' , {
84
+ organization,
85
+ method : 'modal' ,
86
+ } ) ;
87
+ } }
88
+ />
89
+ ) ,
90
+ {
91
+ modalCss : IssueDetailsTourModalCss ,
92
+ onClose : reason => {
93
+ if ( reason ) {
94
+ handleEndTour ( ) ;
95
+ }
96
+ } ,
97
+ }
98
+ ) ;
99
+ }
100
+ } , [
101
+ isPromoVisible ,
102
+ mutateAssistant ,
103
+ organization ,
104
+ endTour ,
105
+ startTour ,
106
+ setLocalTourState ,
107
+ handleEndTour ,
108
+ ] ) ;
109
+
110
+ const resetModal = useCallback ( ( ) => {
111
+ setLocalTourState ( { hasSeen : false } ) ;
112
+ mutateAssistant ( { guide : ISSUE_DETAILS_TOUR_GUIDE_KEY , status : 'restart' } ) ;
113
+ } , [ mutateAssistant , setLocalTourState ] ) ;
114
+
115
+ return { resetModal} ;
116
+ }
117
+
28
118
export function NewIssueExperienceButton ( ) {
29
119
const organization = useOrganization ( ) ;
30
120
const isSuperUser = isActiveSuperuser ( ) ;
31
121
const {
32
- endTour,
33
122
startTour,
34
- currentStepId,
35
123
isRegistered : isTourRegistered ,
36
124
isCompleted : isTourCompleted ,
37
125
} = useIssueDetailsTour ( ) ;
38
- const { mutate : mutateAssistant } = useMutateAssistant ( ) ;
126
+ const { resetModal } = useIssueDetailsPromoModal ( ) ;
39
127
40
128
// XXX: We use a ref to track the previous state of tour completion
41
129
// since we only show the banner when the tour goes from incomplete to complete
@@ -75,51 +163,6 @@ export function NewIssueExperienceButton() {
75
163
} ) ;
76
164
} , [ mutateUserOptions , organization , hasStreamlinedUI , userStreamlinePreference ] ) ;
77
165
78
- // The promotional modal should only appear if:
79
- // - All the steps have been registered
80
- // - The tour has not been completed
81
- // - The tour is not currently active
82
- // - The streamline UI is enabled
83
- const isPromoVisible =
84
- isTourRegistered && ! isTourCompleted && currentStepId === null && hasStreamlinedUI ;
85
-
86
- useEffect ( ( ) => {
87
- if ( isPromoVisible ) {
88
- openModal (
89
- props => (
90
- < IssueDetailsTourModal
91
- handleDismissTour = { ( ) => {
92
- mutateAssistant ( { guide : ISSUE_DETAILS_TOUR_GUIDE_KEY , status : 'dismissed' } ) ;
93
- endTour ( ) ;
94
- trackAnalytics ( 'issue_details.tour.skipped' , { organization} ) ;
95
- props . closeModal ( ) ;
96
- } }
97
- handleStartTour = { ( ) => {
98
- props . closeModal ( ) ;
99
- startTour ( ) ;
100
- trackAnalytics ( 'issue_details.tour.started' , {
101
- organization,
102
- method : 'modal' ,
103
- } ) ;
104
- } }
105
- />
106
- ) ,
107
- {
108
- modalCss : IssueDetailsTourModalCss ,
109
- onClose : reason => {
110
- if ( reason ) {
111
- mutateAssistant ( {
112
- guide : ISSUE_DETAILS_TOUR_GUIDE_KEY ,
113
- status : 'dismissed' ,
114
- } ) ;
115
- endTour ( ) ;
116
- }
117
- } ,
118
- }
119
- ) ;
120
- }
121
- } , [ isPromoVisible , mutateAssistant , organization , endTour , startTour ] ) ;
122
-
123
166
if ( ! hasStreamlinedUI ) {
124
167
return (
125
168
< TryNewButton
@@ -173,9 +216,7 @@ export function NewIssueExperienceButton() {
173
216
key : 'reset-tour-modal' ,
174
217
label : t ( 'Reset tour modal (Superuser only)' ) ,
175
218
hidden : ! isSuperUser || ! isTourCompleted ,
176
- onAction : ( ) => {
177
- mutateAssistant ( { guide : ISSUE_DETAILS_TOUR_GUIDE_KEY , status : 'restart' } ) ;
178
- } ,
219
+ onAction : resetModal ,
179
220
} ,
180
221
] ;
181
222
0 commit comments