@@ -33,6 +33,10 @@ import withProjects from 'app/utils/withProjects';
33
33
34
34
import { getStateFromQuery } from './utils' ;
35
35
36
+ function getProjectIdFromProject ( project ) {
37
+ return parseInt ( project . id , 10 ) ;
38
+ }
39
+
36
40
class GlobalSelectionHeader extends React . Component {
37
41
static propTypes = {
38
42
organization : SentryTypes . Organization ,
@@ -42,6 +46,15 @@ class GlobalSelectionHeader extends React.Component {
42
46
* List of projects to display in project selector
43
47
*/
44
48
projects : PropTypes . arrayOf ( SentryTypes . Project ) . isRequired ,
49
+
50
+ /**
51
+ * A project will be forced from parent component (selection is disabled, and if user
52
+ * does not have multi-project support enabled, it will not try to auto select a project).
53
+ *
54
+ * Project will be specified in the prop `forceProject` (since its data is async)
55
+ */
56
+ shouldForceProject : PropTypes . bool ,
57
+
45
58
/**
46
59
* If a forced project is passed, selection is disabled
47
60
*/
@@ -52,20 +65,40 @@ class GlobalSelectionHeader extends React.Component {
52
65
*/
53
66
selection : SentryTypes . GlobalSelection ,
54
67
55
- // Display Environment selector?
68
+ /**
69
+ * Display Environment selector?
70
+ */
56
71
showEnvironmentSelector : PropTypes . bool ,
57
72
58
- // Display Environment selector?
73
+ /**
74
+ * Display Environment selector?
75
+ */
59
76
showDateSelector : PropTypes . bool ,
60
77
61
- // Disable automatic routing
78
+ /**
79
+ * Disable automatic routing
80
+ */
62
81
hasCustomRouting : PropTypes . bool ,
63
82
64
- // Reset these URL params when we fire actions
65
- // (custom routing only)
83
+ /**
84
+ * Reset these URL params when we fire actions
85
+ * (custom routing only)
86
+ */
66
87
resetParamsOnChange : PropTypes . arrayOf ( PropTypes . string ) ,
67
88
68
- // Props passed to child components //
89
+ /**
90
+ * GlobalSelectionStore is not always initialized (e.g. Group Details) before this is rendered
91
+ *
92
+ * This component intentionally attempts to sync store --> URL Parameter
93
+ * only when mounted, except when this prop changes.
94
+ *
95
+ * XXX: This comes from GlobalSelectionStore and currently does not reset,
96
+ * so it happens at most once. Can add a reset as needed.
97
+ */
98
+ forceUrlSync : PropTypes . bool ,
99
+
100
+ /// Props passed to child components ///
101
+
69
102
/**
70
103
* Show absolute date selectors
71
104
*/
@@ -75,15 +108,6 @@ class GlobalSelectionHeader extends React.Component {
75
108
*/
76
109
showRelative : PropTypes . bool ,
77
110
78
- // GlobalSelectionStore is not always initialized (e.g. Group Details) before this is rendered
79
- //
80
- // This component intentionally attempts to sync store --> URL Parameter
81
- // only when mounted, except when this prop changes.
82
- //
83
- // XXX: This comes from GlobalSelectionStore and currently does not reset,
84
- // so it happens at most once. Can add a reset as needed.
85
- forceUrlSync : PropTypes . bool ,
86
-
87
111
// Callbacks //
88
112
onChangeProjects : PropTypes . func ,
89
113
onUpdateProjects : PropTypes . func ,
@@ -110,7 +134,14 @@ class GlobalSelectionHeader extends React.Component {
110
134
return ;
111
135
}
112
136
113
- const { location, params, organization, selection} = this . props ;
137
+ const {
138
+ location,
139
+ params,
140
+ organization,
141
+ selection,
142
+ shouldForceProject,
143
+ forceProject,
144
+ } = this . props ;
114
145
115
146
const hasMultipleProjectFeature = this . hasMultipleProjectSelection ( ) ;
116
147
@@ -133,12 +164,7 @@ class GlobalSelectionHeader extends React.Component {
133
164
if ( hasMultipleProjectFeature ) {
134
165
updateProjects ( requestedProjects ) ;
135
166
} else {
136
- const allowedProjects =
137
- requestedProjects . length > 0
138
- ? requestedProjects . slice ( 0 , 1 )
139
- : this . getFirstProject ( ) ;
140
- updateProjects ( allowedProjects ) ;
141
- updateParams ( { project : allowedProjects } , this . getRouter ( ) ) ;
167
+ this . enforceSingleProject ( { requestedProjects, shouldForceProject, forceProject} ) ;
142
168
}
143
169
} else if ( params && params . orgId === organization . slug ) {
144
170
// Otherwise, if organization has NOT changed,
@@ -147,19 +173,12 @@ class GlobalSelectionHeader extends React.Component {
147
173
// e.g. when switching to a new view that uses this component,
148
174
// update URL parameters to reflect current store
149
175
const { datetime, environments, projects} = selection ;
176
+ const otherParams = { environment : environments , ...datetime } ;
150
177
151
178
if ( hasMultipleProjectFeature || projects . length === 1 ) {
152
- updateParamsWithoutHistory (
153
- { project : projects , environment : environments , ...datetime } ,
154
- this . getRouter ( )
155
- ) ;
179
+ updateParamsWithoutHistory ( { project : projects , ...otherParams } , this . getRouter ( ) ) ;
156
180
} else {
157
- const allowedProjects = this . getFirstProject ( ) ;
158
- updateProjects ( allowedProjects ) ;
159
- updateParams (
160
- { project : allowedProjects , environment : environments , ...datetime } ,
161
- this . getRouter ( )
162
- ) ;
181
+ this . enforceSingleProject ( { shouldForceProject, forceProject} , otherParams ) ;
163
182
}
164
183
}
165
184
}
@@ -216,13 +235,32 @@ class GlobalSelectionHeader extends React.Component {
216
235
}
217
236
218
237
componentDidUpdate ( prevProps ) {
219
- const { hasCustomRouting, location, forceUrlSync, selection} = this . props ;
238
+ const {
239
+ hasCustomRouting,
240
+ location,
241
+ selection,
242
+ forceUrlSync,
243
+ forceProject,
244
+ } = this . props ;
220
245
221
246
if ( hasCustomRouting ) {
222
247
return ;
223
248
}
224
249
225
- // Kind of gross
250
+ // This means that previously forceProject was falsey (e.g. loading) and now
251
+ // we have the project to force.
252
+ //
253
+ // If user does not have multiple project selection, we need to save the forced
254
+ // project into the store (if project is not in URL params), otherwise
255
+ // there will be weird behavior in this component since it just picks a project
256
+ if ( ! this . hasMultipleProjectSelection ( ) && forceProject && ! prevProps . forceProject ) {
257
+ // Make sure a project isn't specified in query param already, since it should take precendence
258
+ const { project} = getStateFromQuery ( location . query ) ;
259
+ if ( ! project ) {
260
+ this . enforceSingleProject ( { forceProject} ) ;
261
+ }
262
+ }
263
+
226
264
if ( forceUrlSync && ! prevProps . forceUrlSync ) {
227
265
const { project, environment} = getStateFromQuery ( location . query ) ;
228
266
@@ -249,6 +287,38 @@ class GlobalSelectionHeader extends React.Component {
249
287
return new Set ( this . props . organization . features ) . has ( 'global-views' ) ;
250
288
} ;
251
289
290
+ /**
291
+ * If user does not have access to `global-views` (e.g. multi project select), then
292
+ * we update URL params with 1) `props.forceProject`, 2) requested projects from URL params,
293
+ * 3) first project user is a member of from org
294
+ */
295
+ enforceSingleProject = (
296
+ { requestedProjects, shouldForceProject, forceProject} = { } ,
297
+ otherParams
298
+ ) => {
299
+ let newProject ;
300
+
301
+ // This is the case where we *want* to force project, but we are still loading
302
+ // the forced project's details
303
+ if ( shouldForceProject && ! forceProject ) {
304
+ return ;
305
+ }
306
+
307
+ if ( forceProject ) {
308
+ // this takes precendence over the other options
309
+ newProject = [ getProjectIdFromProject ( forceProject ) ] ;
310
+ } else if ( requestedProjects && requestedProjects . length > 0 ) {
311
+ // If there is a list of projects from URL params, select first project from that list
312
+ newProject = [ requestedProjects [ 0 ] ] ;
313
+ } else {
314
+ // Otherwise, get first project from org that the user is a member of
315
+ newProject = this . getFirstProject ( ) ;
316
+ }
317
+
318
+ updateProjects ( newProject ) ;
319
+ updateParamsWithoutHistory ( { project : newProject , ...otherParams } , this . getRouter ( ) ) ;
320
+ } ;
321
+
252
322
/**
253
323
* Identifies the query params (that are relevant to this component) that have changed
254
324
*
@@ -390,7 +460,7 @@ class GlobalSelectionHeader extends React.Component {
390
460
391
461
getFirstProject = ( ) => {
392
462
return flatten ( this . getProjects ( ) )
393
- . map ( p => parseInt ( p . id , 10 ) )
463
+ . map ( getProjectIdFromProject )
394
464
. slice ( 0 , 1 ) ;
395
465
} ;
396
466
@@ -412,6 +482,7 @@ class GlobalSelectionHeader extends React.Component {
412
482
render ( ) {
413
483
const {
414
484
className,
485
+ shouldForceProject,
415
486
forceProject,
416
487
organization,
417
488
showAbsolute,
@@ -430,9 +501,10 @@ class GlobalSelectionHeader extends React.Component {
430
501
return (
431
502
< Header className = { className } >
432
503
< HeaderItemPosition >
433
- { forceProject && this . getBackButton ( ) }
504
+ { shouldForceProject && this . getBackButton ( ) }
434
505
< MultipleProjectSelector
435
506
organization = { organization }
507
+ shouldForceProject = { shouldForceProject }
436
508
forceProject = { forceProject }
437
509
projects = { projects }
438
510
nonMemberProjects = { nonMemberProjects }
0 commit comments