1
- import { ChangeEvent , FC , useState , useCallback } from "react" ;
2
- import { Form , Container , Button , Row , Col } from "react-bootstrap" ;
1
+ import { ChangeEvent , FC , useState , useCallback , useMemo } from "react" ;
2
+ import { Form , Container , Button , Row , Col , Modal } from "react-bootstrap" ;
3
3
4
4
import { useToastsContext } from "../hooks/useToasts" ;
5
5
import DropdownMenu from "./DropdownMenu" ;
6
6
7
+ type EnrollWithFeatureConfigResult =
8
+ | {
9
+ enrolled : true ;
10
+ error : null ;
11
+ }
12
+ | {
13
+ enrolled : false ;
14
+ error : {
15
+ slugExistsInStore : boolean ;
16
+ activeEnrollment : string | null ;
17
+ } ;
18
+ } ;
19
+
7
20
const FeatureConfigPage : FC = ( ) => {
8
21
const [ jsonInput , setJsonInput ] = useState ( "" ) ;
9
22
const [ selectedFeatureId , setSelectedFeatureId ] = useState ( "" ) ;
10
23
const [ isRollout , setIsRollout ] = useState ( false ) ;
24
+ const [ enrollError , setEnrollError ] =
25
+ useState < EnrollWithFeatureConfigResult [ "error" ] > ( null ) ;
11
26
const { addToast } = useToastsContext ( ) ;
12
27
28
+ const slug = useMemo ( ( ) => {
29
+ return `nimbus-devtools-${ selectedFeatureId } -${
30
+ isRollout ? "rollout" : "experiment"
31
+ } `;
32
+ } , [ selectedFeatureId , isRollout ] ) ;
33
+
13
34
const handleInputChange = useCallback (
14
35
( event : ChangeEvent < HTMLTextAreaElement > ) => {
15
36
setJsonInput ( event . target . value ) ;
@@ -28,32 +49,99 @@ const FeatureConfigPage: FC = () => {
28
49
[ ] ,
29
50
) ;
30
51
31
- const handleEnrollClick = useCallback ( async ( ) => {
32
- if ( selectedFeatureId === "" ) {
33
- addToast ( { message : "Invalid Input: Select feature" , variant : "danger" } ) ;
34
- } else if ( jsonInput === "" ) {
35
- addToast ( { message : "Invalid Input: Enter JSON" , variant : "danger" } ) ;
36
- } else {
37
- try {
38
- const result = await browser . experiments . nimbus . enrollWithFeatureConfig (
39
- selectedFeatureId ,
40
- JSON . parse ( jsonInput ) as object ,
41
- isRollout ,
42
- ) ;
43
-
44
- if ( result ) {
45
- addToast ( { message : "Enrollment successful" , variant : "success" } ) ;
46
- } else {
47
- addToast ( { message : "Enrollment failed" , variant : "danger" } ) ;
48
- }
49
- } catch ( error ) {
52
+ const handleEnrollClick = useCallback (
53
+ async (
54
+ event ?: React . MouseEvent < HTMLButtonElement > ,
55
+ forceEnroll = false ,
56
+ ) => {
57
+ event ?. preventDefault ( ) ;
58
+
59
+ if ( selectedFeatureId === "" ) {
50
60
addToast ( {
51
- message : `Error enrolling into experiment: ${ ( error as Error ) . message ?? String ( error ) } ` ,
61
+ message : "Invalid Input: Select feature" ,
52
62
variant : "danger" ,
53
63
} ) ;
64
+ } else if ( jsonInput === "" ) {
65
+ addToast ( { message : "Invalid Input: Enter JSON" , variant : "danger" } ) ;
66
+ } else {
67
+ try {
68
+ const result : EnrollWithFeatureConfigResult =
69
+ await browser . experiments . nimbus . enrollWithFeatureConfig (
70
+ selectedFeatureId ,
71
+ JSON . parse ( jsonInput ) as object ,
72
+ isRollout ,
73
+ forceEnroll ,
74
+ ) ;
75
+
76
+ if ( result . enrolled ) {
77
+ addToast ( { message : "Enrollment successful" , variant : "success" } ) ;
78
+ } else if ( result . error ) {
79
+ setEnrollError ( result . error ) ;
80
+ }
81
+ } catch ( error ) {
82
+ addToast ( {
83
+ message : `Error enrolling into experiment: ${
84
+ ( error as Error ) . message ?? String ( error )
85
+ } `,
86
+ variant : "danger" ,
87
+ } ) ;
88
+ }
54
89
}
90
+ } ,
91
+ [ jsonInput , selectedFeatureId , isRollout , addToast ] ,
92
+ ) ;
93
+
94
+ const handleModalConfirm = useCallback ( async ( ) => {
95
+ setEnrollError ( null ) ;
96
+ await handleEnrollClick ( null , true ) ;
97
+ } , [ handleEnrollClick , setEnrollError ] ) ;
98
+
99
+ const handleModalClose = useCallback ( ( ) => {
100
+ setEnrollError ( null ) ;
101
+ } , [ setEnrollError ] ) ;
102
+
103
+ function EnrollmentError (
104
+ slug : string ,
105
+ enrollError : EnrollWithFeatureConfigResult [ "error" ] ,
106
+ ) {
107
+ if ( ! enrollError ) {
108
+ return null ;
109
+ }
110
+ const { activeEnrollment, slugExistsInStore } = enrollError ;
111
+
112
+ if ( activeEnrollment && slugExistsInStore ) {
113
+ return (
114
+ < p >
115
+ There already is an enrollment for the feature:{ " " }
116
+ < strong > { slug } </ strong > . Would you like to proceed with force
117
+ enrollment by unenrolling, deleting, and re-enrolling into the new
118
+ configuration?
119
+ </ p >
120
+ ) ;
121
+ }
122
+
123
+ if ( activeEnrollment ) {
124
+ return (
125
+ < p >
126
+ There is an active enrollment for the feature: < strong > { slug } </ strong >
127
+ . Would you like to unenroll from the active enrollment and re-enroll
128
+ into the new configuration?
129
+ </ p >
130
+ ) ;
131
+ }
132
+
133
+ if ( slugExistsInStore ) {
134
+ return (
135
+ < p >
136
+ There is an inactive enrollment stored for the feature:{ " " }
137
+ < strong > { slug } </ strong > . Would you like to delete the inactive
138
+ enrollment and re-enroll into the new configuration?
139
+ </ p >
140
+ ) ;
55
141
}
56
- } , [ jsonInput , selectedFeatureId , isRollout , addToast ] ) ;
142
+
143
+ return null ;
144
+ }
57
145
58
146
return (
59
147
< Container className = "main-content p-2 overflow-hidden" >
@@ -90,6 +178,21 @@ const FeatureConfigPage: FC = () => {
90
178
Enroll
91
179
</ Button >
92
180
</ Form >
181
+
182
+ < Modal show = { ! ! enrollError } onHide = { handleModalClose } >
183
+ < Modal . Header closeButton >
184
+ < Modal . Title > Force Enrollment</ Modal . Title >
185
+ </ Modal . Header >
186
+ < Modal . Body > { EnrollmentError ( slug , enrollError ) } </ Modal . Body >
187
+ < Modal . Footer >
188
+ < Button variant = "secondary" onClick = { handleModalClose } >
189
+ Cancel
190
+ </ Button >
191
+ < Button variant = "danger" onClick = { handleModalConfirm } >
192
+ Force Enroll
193
+ </ Button >
194
+ </ Modal . Footer >
195
+ </ Modal >
93
196
</ Container >
94
197
) ;
95
198
} ;
0 commit comments