14
14
* limitations under the License.
15
15
*/
16
16
17
- import React , { Fragment , useEffect , useMemo , useRef , useState } from 'react'
17
+ import React , { useEffect , useMemo , useRef , useState } from 'react'
18
18
import { generatePath , useHistory , useLocation , useParams } from 'react-router-dom'
19
19
import ReactSelect , { GroupBase , InputActionMeta } from 'react-select'
20
20
import Select , { FormatOptionLabelMeta } from 'react-select/base'
21
21
import DOMPurify from 'dompurify'
22
22
23
23
import {
24
- ApiResourceGroupType ,
24
+ capitalizeFirstLetter ,
25
25
highlightSearchText ,
26
26
K8S_EMPTY_GROUP ,
27
27
Nodes ,
28
+ NodeType ,
28
29
ReactSelectInputAction ,
29
30
RESOURCE_BROWSER_ROUTES ,
31
+ TreeNode ,
32
+ TreeView ,
30
33
URL_FILTER_KEYS ,
31
34
useRegisterShortcut ,
32
35
} from '@devtron-labs/devtron-fe-common-lib'
33
36
34
- import { ReactComponent as ICExpand } from '../../../assets/icons/ic-expand.svg'
35
- import { AggregationKeys } from '../../app/types'
36
- import { KIND_SEARCH_COMMON_STYLES , ResourceBrowserTabsId , SIDEBAR_KEYS } from '../Constants'
37
- import { K8SObjectChildMapType , K8SObjectMapType , K8sObjectOptionType , SidebarType } from '../Types'
38
- import {
39
- convertK8sObjectMapToOptionsList ,
40
- convertResourceGroupListToK8sObjectList ,
41
- getK8SObjectMapAfterGroupHeadingClick ,
42
- } from '../Utils'
43
- import { KindSearchClearIndicator , KindSearchValueContainer , SidebarChildButton } from './ResourceList.component'
37
+ import { KIND_SEARCH_COMMON_STYLES , ResourceBrowserTabsId } from '../Constants'
38
+ import { K8sObjectOptionType , RBResourceSidebarDataAttributeType , SidebarType } from '../Types'
39
+ import { convertK8sObjectMapToOptionsList , convertResourceGroupListToK8sObjectList } from '../Utils'
40
+ import { KindSearchClearIndicator , KindSearchValueContainer } from './ResourceList.component'
44
41
import { K8sResourceListURLParams } from './types'
42
+ import { getRBSidebarTreeViewNodeId , getRBSidebarTreeViewNodes } from './utils'
45
43
46
44
const Sidebar = ( { apiResources, selectedResource, updateK8sResourceTab, updateTabLastSyncMoment } : SidebarType ) => {
47
45
const { registerShortcut, unregisterShortcut } = useRegisterShortcut ( )
@@ -50,8 +48,7 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
50
48
const { clusterId, kind, group } = useParams < K8sResourceListURLParams > ( )
51
49
const [ searchText , setSearchText ] = useState ( '' )
52
50
/* NOTE: apiResources prop will only change after a component mount/dismount */
53
- const [ list , setList ] = useState ( convertResourceGroupListToK8sObjectList ( apiResources || null , kind ) )
54
- const preventScrollRef = useRef ( false )
51
+ const list = convertResourceGroupListToK8sObjectList ( apiResources || null , kind )
55
52
const searchInputRef = useRef < Select < K8sObjectOptionType , false , GroupBase < K8sObjectOptionType > > > ( null )
56
53
const k8sObjectOptionsList = useMemo (
57
54
( ) => convertK8sObjectMapToOptionsList ( convertResourceGroupListToK8sObjectList ( apiResources || null , kind ) ) ,
@@ -96,62 +93,29 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
96
93
}
97
94
} , [ ] )
98
95
99
- const getGroupHeadingClickHandler =
100
- ( preventCollapse = false , preventScroll = false ) =>
101
- ( e : React . MouseEvent < HTMLButtonElement > | { currentTarget : { dataset : { groupName : string } } } ) => {
102
- preventScrollRef . current = preventScroll
103
- setList ( getK8SObjectMapAfterGroupHeadingClick ( e , list , preventCollapse ) )
104
- }
105
-
106
- const selectNode = (
107
- e : React . MouseEvent < HTMLButtonElement > | { currentTarget : Pick < K8sObjectOptionType , 'dataset' > } ,
108
- groupName ?: string ,
109
- ) : void => {
110
- const _selectedKind = e . currentTarget . dataset . kind . toLowerCase ( )
111
- const _selectedGroup = e . currentTarget . dataset . group . toLowerCase ( )
112
-
96
+ const selectNode = ( selectedKind : string , selectedGroup : string ) : void => {
113
97
const params = new URLSearchParams ( location . search )
114
98
params . delete ( URL_FILTER_KEYS . PAGE_NUMBER )
115
99
params . delete ( URL_FILTER_KEYS . SORT_BY )
116
100
params . delete ( URL_FILTER_KEYS . SORT_ORDER )
117
- if ( _selectedKind !== Nodes . Event . toLowerCase ( ) ) {
101
+ if ( selectedKind !== Nodes . Event . toLowerCase ( ) ) {
118
102
params . delete ( 'eventType' )
119
103
}
120
104
const path = generatePath ( RESOURCE_BROWSER_ROUTES . K8S_RESOURCE_LIST , {
121
105
clusterId,
122
- kind : _selectedKind ,
123
- group : _selectedGroup || K8S_EMPTY_GROUP ,
106
+ kind : selectedKind ,
107
+ group : selectedGroup || K8S_EMPTY_GROUP ,
124
108
} )
125
109
126
110
if ( path === location . pathname ) {
127
111
return
128
112
}
129
113
130
114
const _url = `${ path } ?${ params . toString ( ) } `
131
-
132
- updateK8sResourceTab ( { url : _url , dynamicTitle : e . currentTarget . dataset . kind , retainSearchParams : true } )
115
+ updateK8sResourceTab ( { url : _url , dynamicTitle : capitalizeFirstLetter ( selectedKind ) , retainSearchParams : true } )
133
116
updateTabLastSyncMoment ( ResourceBrowserTabsId . k8s_Resources )
134
117
135
118
push ( _url )
136
-
137
- /**
138
- * If groupName present then kind selection is from search dropdown,
139
- * - Expand parent group if not already expanded
140
- * - Auto scroll to selection
141
- * Else reset prevent scroll to true
142
- */
143
- if ( groupName ) {
144
- getGroupHeadingClickHandler (
145
- true ,
146
- false ,
147
- ) ( {
148
- currentTarget : {
149
- dataset : {
150
- groupName,
151
- } ,
152
- } ,
153
- } )
154
- }
155
119
}
156
120
157
121
useEffect ( ( ) => {
@@ -169,89 +133,9 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
169
133
( option . dataset . group || K8S_EMPTY_GROUP ) . toLowerCase ( ) === lowercasedGroup ,
170
134
) ?? k8sObjectOptionsList [ 0 ]
171
135
/* NOTE: if nodeType doesn't match the selectedResource kind, set it accordingly */
172
- selectNode (
173
- {
174
- currentTarget : {
175
- dataset : match . dataset ,
176
- } ,
177
- } ,
178
- match . groupName ,
179
- )
136
+ selectNode ( match . dataset . kind . toLowerCase ( ) , match . dataset . group . toLowerCase ( ) )
180
137
} , [ kind , group , k8sObjectOptionsList ] )
181
138
182
- const selectedChildRef : React . Ref < HTMLButtonElement > = ( node ) => {
183
- /**
184
- * NOTE: all list items will be passed this ref callback
185
- * The correct node will get scrolled into view */
186
- if ( node ?. dataset . selected !== 'true' || preventScrollRef . current ) {
187
- return
188
- }
189
- node ?. scrollIntoView ( { block : 'center' } )
190
- }
191
-
192
- const renderChild = ( childData : ApiResourceGroupType , useGroupName = false ) => {
193
- const nodeName = useGroupName && childData . gvk . Group ? childData . gvk . Group : childData . gvk . Kind
194
- const isSelected =
195
- useGroupName && childData . gvk . Group
196
- ? selectedResource ?. gvk ?. Group === childData . gvk . Group &&
197
- selectedResource ?. gvk ?. Kind === childData . gvk . Kind
198
- : selectedResource ?. gvk ?. Kind === childData . gvk . Kind &&
199
- ( selectedResource ?. gvk ?. Group === childData . gvk . Group ||
200
- selectedResource ?. gvk ?. Group === K8S_EMPTY_GROUP )
201
- return (
202
- < SidebarChildButton
203
- parentRef = { selectedChildRef }
204
- text = { nodeName }
205
- group = { childData . gvk . Group }
206
- version = { childData . gvk . Version }
207
- kind = { childData . gvk . Kind }
208
- namespaced = { childData . namespaced }
209
- isSelected = { isSelected }
210
- onClick = { selectNode }
211
- />
212
- )
213
- }
214
-
215
- const renderK8sResourceChildren = ( key : string , value : K8SObjectChildMapType , k8sObject : K8SObjectMapType ) => {
216
- const keyLowerCased = key . toLowerCase ( )
217
- if (
218
- keyLowerCased === 'node' ||
219
- keyLowerCased === SIDEBAR_KEYS . namespaceGVK . Kind . toLowerCase ( ) ||
220
- keyLowerCased === SIDEBAR_KEYS . eventGVK . Kind . toLowerCase ( )
221
- ) {
222
- return null
223
- }
224
- if ( value . data . length === 1 ) {
225
- return renderChild ( value . data [ 0 ] )
226
- }
227
- return (
228
- < Fragment key = { `${ k8sObject . name } /${ key } -child` } >
229
- < button
230
- type = "button"
231
- className = "dc__unset-button-styles"
232
- data-group-name = { `${ k8sObject . name } /${ key } ` }
233
- onClick = { getGroupHeadingClickHandler ( false , true ) as React . MouseEventHandler < HTMLButtonElement > }
234
- >
235
- < div className = "flex pointer dc__align-left" >
236
- < ICExpand
237
- className = { `${ value . isExpanded ? 'fcn-9' : 'fcn-5' } rotate icon-dim-24 pointer` }
238
- style = { {
239
- [ '--rotateBy' as string ] : value . isExpanded ? '90deg' : '0deg' ,
240
- } }
241
- />
242
- < span className = "fs-13 cn-9 fw-6 pointer w-100 pt-6 pb-6" > { key } </ span >
243
- </ div >
244
- </ button >
245
- < div className = "pl-20 flexbox-col" >
246
- { value . isExpanded &&
247
- value . data . map ( ( _child ) => (
248
- < React . Fragment key = { _child . gvk . Group } > { renderChild ( _child , true ) } </ React . Fragment >
249
- ) ) }
250
- </ div >
251
- </ Fragment >
252
- )
253
- }
254
-
255
139
const handleInputChange = ( newValue : string , actionMeta : InputActionMeta ) : void => {
256
140
if ( actionMeta . action !== ReactSelectInputAction . inputChange ) {
257
141
return
@@ -268,14 +152,11 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
268
152
if ( ! option ) {
269
153
return
270
154
}
271
- selectNode (
272
- {
273
- currentTarget : {
274
- dataset : option . dataset ,
275
- } ,
276
- } ,
277
- option . groupName ,
278
- )
155
+ selectNode ( option . dataset . kind . toLowerCase ( ) , option . dataset . group . toLowerCase ( ) )
156
+ }
157
+
158
+ const handleTreeViewNodeSelect = ( node : TreeNode < RBResourceSidebarDataAttributeType > ) : void => {
159
+ selectNode ( node . dataAttributes ?. [ 'data-kind' ] , node . dataAttributes ?. [ 'data-group' ] )
279
160
}
280
161
281
162
const formatOptionLabel = (
@@ -315,6 +196,8 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
315
196
316
197
const noOptionsMessage = ( ) => 'No matching kind'
317
198
199
+ const treeViewNodes = getRBSidebarTreeViewNodes ( list )
200
+
318
201
return (
319
202
< div className = "w-250 dc__no-shrink dc__overflow-hidden flexbox-col" >
320
203
< div className = "k8s-object-kind-search bg__primary pt-16 pb-8 px-10 w-100 cursor" >
@@ -348,82 +231,13 @@ const Sidebar = ({ apiResources, selectedResource, updateK8sResourceTab, updateT
348
231
</ div >
349
232
350
233
< div className = "dc__overflow-auto flexbox-col flex-grow-1 dc__border-top-n1 p-8 dc__user-select-none" >
351
- < div className = "pb-8 flexbox-col" >
352
- { ! ! list ?. size && ! ! list . get ( AggregationKeys . Nodes ) && (
353
- < SidebarChildButton
354
- parentRef = { selectedChildRef }
355
- text = { SIDEBAR_KEYS . nodes }
356
- group = { SIDEBAR_KEYS . nodeGVK . Group }
357
- version = { SIDEBAR_KEYS . nodeGVK . Version }
358
- kind = { SIDEBAR_KEYS . nodeGVK . Kind }
359
- namespaced = { false }
360
- isSelected = { kind === SIDEBAR_KEYS . nodeGVK . Kind . toLowerCase ( ) }
361
- onClick = { selectNode }
362
- />
363
- ) }
364
- { ! ! list ?. size && ! ! list . get ( AggregationKeys . Events ) && (
365
- < SidebarChildButton
366
- parentRef = { selectedChildRef }
367
- text = { SIDEBAR_KEYS . events }
368
- group = { SIDEBAR_KEYS . eventGVK . Group }
369
- version = { SIDEBAR_KEYS . eventGVK . Version }
370
- kind = { SIDEBAR_KEYS . eventGVK . Kind }
371
- namespaced
372
- isSelected = { kind === SIDEBAR_KEYS . eventGVK . Kind . toLowerCase ( ) }
373
- onClick = { selectNode }
374
- />
375
- ) }
376
- { ! ! list ?. size && ! ! list . get ( AggregationKeys . Namespaces ) && (
377
- < SidebarChildButton
378
- parentRef = { selectedChildRef }
379
- text = { SIDEBAR_KEYS . namespaces }
380
- group = { SIDEBAR_KEYS . namespaceGVK . Group }
381
- version = { SIDEBAR_KEYS . namespaceGVK . Version }
382
- kind = { SIDEBAR_KEYS . namespaceGVK . Kind }
383
- namespaced = { false }
384
- isSelected = { kind === SIDEBAR_KEYS . namespaceGVK . Kind . toLowerCase ( ) }
385
- onClick = { selectNode }
386
- />
387
- ) }
388
- </ div >
389
- { ! ! list ?. size &&
390
- [ ...list . values ( ) ] . map ( ( k8sObject ) =>
391
- k8sObject . name === AggregationKeys . Events ||
392
- k8sObject . name === AggregationKeys . Namespaces ||
393
- k8sObject . name === AggregationKeys . Nodes ? null : (
394
- < div key = { `${ k8sObject . name } -parent` } >
395
- < button
396
- type = "button"
397
- className = { `dc__unset-button-styles dc__zi-1 bg__primary w-100 ${ k8sObject . isExpanded ? 'dc__position-sticky' : '' } ` }
398
- style = { { top : '-8px' } }
399
- data-group-name = { k8sObject . name }
400
- onClick = { getGroupHeadingClickHandler ( false , true ) }
401
- >
402
- < div className = "flex pointer dc__align-left" >
403
- < ICExpand
404
- className = { `${ k8sObject . isExpanded ? 'fcn-9' : 'fcn-5' } rotate icon-dim-24 pointer` }
405
- style = { {
406
- [ '--rotateBy' as string ] : ! k8sObject . isExpanded ? '0deg' : '90deg' ,
407
- } }
408
- />
409
- < span
410
- className = "fs-13 cn-9 fw-6 pointer w-100 pt-6 pb-6"
411
- data-testid = { `k8sObject-${ k8sObject . name } ` }
412
- >
413
- { k8sObject . name }
414
- </ span >
415
- </ div >
416
- </ button >
417
- { k8sObject . isExpanded && (
418
- < div className = "pl-20 flexbox-col" >
419
- { [ ...k8sObject . child . entries ( ) ] . map ( ( [ key , value ] ) =>
420
- renderK8sResourceChildren ( key , value , k8sObject ) ,
421
- ) }
422
- </ div >
423
- ) }
424
- </ div >
425
- ) ,
234
+ < TreeView < RBResourceSidebarDataAttributeType >
235
+ nodes = { treeViewNodes }
236
+ selectedId = { getRBSidebarTreeViewNodeId (
237
+ selectedResource ?. gvk || { Group : '' , Version : '' , Kind : '' as NodeType } ,
426
238
) }
239
+ onSelect = { handleTreeViewNodeSelect }
240
+ />
427
241
</ div >
428
242
</ div >
429
243
)
0 commit comments