1
1
import React , { useState , useEffect } from 'react'
2
- import { Progressing , ResizableTextarea , ToastBody , noop , showError } from '@devtron-labs/devtron-fe-common-lib'
2
+ import { ConditionalWrap , Progressing , ResizableTextarea , TippyTheme , ToastBody , noop , showError } from '@devtron-labs/devtron-fe-common-lib'
3
3
import YAML from 'yaml'
4
4
import { DEPLOYMENT_HISTORY_CONFIGURATION_LIST_MAP , PATTERNS } from '../../config'
5
5
import { ReactComponent as Dropdown } from '../../assets/icons/ic-chevron-down.svg'
@@ -28,9 +28,10 @@ import { DeploymentHistoryDetail } from '../app/details/cdDetails/cd.type'
28
28
import { prepareHistoryData } from '../app/details/cdDetails/service'
29
29
import './ConfigMapSecret.scss'
30
30
import { getCMSecret , getConfigMapList , getSecretList } from './service'
31
- import { useParams } from 'react-router-dom'
31
+ import { useHistory , useParams , useRouteMatch } from 'react-router-dom'
32
32
import { toast } from 'react-toastify'
33
33
import Tippy from '@tippyjs/react'
34
+ import { followCursor } from 'tippy.js'
34
35
35
36
const ConfigToolbar = importComponentFromFELibrary ( 'ConfigToolbar' )
36
37
const ApproveRequestTippy = importComponentFromFELibrary ( 'ApproveRequestTippy' )
@@ -114,12 +115,13 @@ export function ConfigMapSecretContainer({
114
115
parentName,
115
116
reloadEnvironments,
116
117
} : ConfigMapSecretProps ) {
117
- const { appId, envId } = useParams < { appId ; envId } > ( )
118
- const [ collapsed , toggleCollapse ] = useState ( true )
118
+ const { appId, envId, name } = useParams < { appId ; envId ; name } > ( )
119
+ const history = useHistory ( )
120
+ const match = useRouteMatch ( )
119
121
const [ isLoader , setLoader ] = useState < boolean > ( false )
120
122
const [ draftData , setDraftData ] = useState ( null )
121
123
const [ selectedTab , setSelectedTab ] = useState ( data ?. draftState === 4 ? 2 : 3 )
122
- const [ abortController , setAbortController ] = useState ( new AbortController ( ) ) ;
124
+ const [ abortController , setAbortController ] = useState ( new AbortController ( ) )
123
125
124
126
let cmSecretStateLabel = ! data ?. isNew ? CM_SECRET_STATE . BASE : CM_SECRET_STATE . UNPUBLISHED
125
127
if ( isOverrideView ) {
@@ -129,18 +131,32 @@ export function ConfigMapSecretContainer({
129
131
cmSecretStateLabel = ! data ?. isNew ? CM_SECRET_STATE . ENV : CM_SECRET_STATE . UNPUBLISHED
130
132
}
131
133
}
134
+
135
+ useEffect ( ( ) => {
136
+ if ( title !== '' && title === name ) {
137
+ getData ( )
138
+ }
139
+ } , [ ] )
132
140
133
141
const getData = async ( ) => {
134
142
try {
135
143
abortController . abort ( )
136
- const newAbortController = new AbortController ( ) ;
137
- setAbortController ( newAbortController ) ;
144
+ const newAbortController = new AbortController ( )
138
145
setLoader ( true )
146
+ setAbortController ( newAbortController )
139
147
const [ _draftData , _cmSecretData ] = await Promise . allSettled ( [
140
148
isProtected && getDraftByResourceName
141
- ? getDraftByResourceName ( appId , envId ?? - 1 , componentType === 'secret' ? 2 : 1 , data . name , newAbortController . signal )
149
+ ? getDraftByResourceName (
150
+ appId ,
151
+ envId ?? - 1 ,
152
+ componentType === 'secret' ? 2 : 1 ,
153
+ title ,
154
+ newAbortController . signal ,
155
+ )
156
+ : null ,
157
+ ! data ?. isNew
158
+ ? getCMSecret ( componentType , id , appId , title , envId , newAbortController . signal )
142
159
: null ,
143
- ! data ?. isNew ? getCMSecret ( componentType , id , appId , data ?. name , envId , newAbortController . signal ) : null ,
144
160
] )
145
161
let draftId , draftState
146
162
if (
@@ -181,6 +197,7 @@ export function ConfigMapSecretContainer({
181
197
} else {
182
198
toast . error ( `The ${ componentType } '${ data ?. name } ' has been deleted` )
183
199
update ( index , null )
200
+ redirectURLToInitial ( )
184
201
}
185
202
} else if (
186
203
cmSecretStateLabel === CM_SECRET_STATE . UNPUBLISHED &&
@@ -193,10 +210,14 @@ export function ConfigMapSecretContainer({
193
210
} else if ( _draftData . value . result . draftState === 2 ) {
194
211
toast . error ( `The ${ componentType } '${ data ?. name } ' has been deleted` )
195
212
update ( index , null )
213
+ redirectURLToInitial ( )
196
214
}
197
215
}
198
- if ( ( _cmSecretData ?. status === 'fulfilled' && _cmSecretData ?. value !== null ) || ( _draftData ?. status === 'fulfilled' && _draftData ?. value !== null ) ) {
199
- toggleCollapse ( false )
216
+ if (
217
+ ( _cmSecretData ?. status === 'fulfilled' && _cmSecretData ?. value !== null ) ||
218
+ ( _draftData ?. status === 'fulfilled' && _draftData ?. value !== null )
219
+ ) {
220
+ setLoader ( true )
200
221
}
201
222
if (
202
223
( _cmSecretData ?. status === 'rejected' && _cmSecretData ?. reason ?. code === 403 ) ||
@@ -213,20 +234,32 @@ export function ConfigMapSecretContainer({
213
234
}
214
235
}
215
236
237
+ const redirectURLToInitial = ( urlTo : string = '' ) => {
238
+ const componentTypeName = componentType === 'secret' ? 'secrets' : 'configmap'
239
+ const urlPrefix = match . url . split ( componentTypeName ) [ 0 ]
240
+ history . push ( `${ urlPrefix } ${ componentTypeName } /${ urlTo } ` )
241
+ }
242
+
216
243
const updateCollapsed = ( _collapsed ?: boolean ) : void => {
217
- if ( _collapsed !== undefined ) {
218
- toggleCollapse ( _collapsed )
244
+ if ( ! title ) {
245
+ //Redirect and Add config map & secret
246
+ if ( name === 'create' ) {
247
+ toggleDraftComments ( null )
248
+ setDraftData ( null )
249
+ return redirectURLToInitial ( )
250
+ }
251
+ return redirectURLToInitial ( 'create' )
219
252
} else {
220
- if ( collapsed && data ?. name ) {
221
- getData ( )
253
+ //Redirect and Open existing config map & secret
254
+ if ( name === title ) {
255
+ toggleDraftComments ( null )
256
+ setDraftData ( null )
257
+ return redirectURLToInitial ( )
222
258
} else {
223
- toggleCollapse ( ! collapsed )
224
- if ( ! collapsed ) {
225
- toggleDraftComments ( null )
226
- setDraftData ( null )
227
- }
259
+ getData ( )
260
+ return redirectURLToInitial ( title )
228
261
}
229
- }
262
+ }
230
263
}
231
264
232
265
const handleTabSelection = ( index : number ) : void => {
@@ -255,6 +288,7 @@ export function ConfigMapSecretContainer({
255
288
}
256
289
257
290
const renderDetails = ( ) : JSX . Element => {
291
+ if ( ( name && ( ( ! title && name !== 'create' ) || ( title && name !== title ) ) ) || ! name ) return null
258
292
if ( title && isProtected && draftData ?. draftId ) {
259
293
return (
260
294
< >
@@ -323,7 +357,7 @@ export function ConfigMapSecretContainer({
323
357
}
324
358
325
359
const renderDraftState = ( ) : JSX . Element => {
326
- if ( collapsed ) {
360
+ if ( title !== name ) {
327
361
if ( data . draftState === 1 ) {
328
362
return < i className = "mr-10 cr-5" > In draft</ i >
329
363
} else if ( data . draftState === 4 ) {
@@ -334,53 +368,77 @@ export function ConfigMapSecretContainer({
334
368
return null
335
369
}
336
370
337
- const handleCMSecretClick = ( ) => {
371
+ const handleCMSecretClick = ( event ) => {
338
372
if ( title && isProtected && draftData ?. draftId ) {
339
373
setSelectedTab ( draftData . draftState === 4 ? 2 : 3 )
340
374
}
341
- updateCollapsed ( )
375
+ updateCollapsed ( )
342
376
}
343
377
378
+ const showBlurEffect = name && ( ( ! title && name !== 'create' ) || ( title && name !== title ) )
379
+
344
380
return (
345
381
< >
346
- < section
347
- className = { `pt-16 dc__border bcn-0 br-8 ${ title ? 'mb-16' : 'en-3 bw-1 dashed mb-20' } ${
348
- reduceOpacity ? 'dc__disable-click dc__blur-1_5' : ''
349
- } `}
382
+ < ConditionalWrap
383
+ condition = { showBlurEffect }
384
+ wrap = { ( children ) => (
385
+ < Tippy
386
+ theme = { TippyTheme . black }
387
+ followCursor = { true }
388
+ plugins = { [ followCursor ] }
389
+ arrow = { true }
390
+ animation = "shift-toward-subtle"
391
+ placement = 'top'
392
+ content = { `Collapse opened ${ componentType === 'secret' ? ' Secret' : ' ConfigMap' } first` }
393
+ >
394
+ < div > { children } </ div >
395
+ </ Tippy >
396
+ ) }
350
397
>
351
- < article
352
- className = "dc__configuration-list pointer pr-16 pl-16 mb-16"
353
- onClick = { handleCMSecretClick }
354
- data-testid = "click-to-add-configmaps-secret"
398
+ < div className = { `${ showBlurEffect ? 'cursor-not-allowed' : 'cursor' } ` } >
399
+ < section
400
+ className = { `pt-16 dc__border bcn-0 br-8 ${ title ? 'mb-16' : 'en-3 bw-1 dashed mb-20' } ${
401
+ reduceOpacity || showBlurEffect ? 'dc__disable-click dc__blur-1_5' : ''
402
+ } `}
355
403
>
356
- { renderIcon ( ) }
357
- < div
358
- data-testid = { `add- ${ componentType } -button` }
359
- className = { `flex left lh-20 ${ ! title ? 'fw-5 fs-14 cb-5' : 'fw-5 fs-14 cn-9' } ` }
404
+ < article
405
+ className = "dc__configuration-list pr-16 pl-16 mb-16"
406
+ onClick = { handleCMSecretClick }
407
+ data-testid = "click-to-add-configmaps-secret"
360
408
>
361
- { title || `Add ${ componentType === 'secret' ? 'Secret' : 'ConfigMap' } ` }
362
- { cmSecretStateLabel && < div className = "flex tag ml-12" > { cmSecretStateLabel } </ div > }
363
- </ div >
364
- { title && (
365
- < div className = "flex right" >
366
- { isProtected && (
367
- < >
368
- { renderDraftState ( ) }
369
- < ProtectedIcon className = "icon-dim-20 mr-10 fcv-5" />
370
- </ >
371
- ) }
372
- { isLoader ? (
373
- < span style = { { width : '20px' } } >
374
- < Progressing />
375
- </ span >
376
- ) : (
377
- < Dropdown className = { `icon-dim-20 rotate ${ collapsed ? '' : 'dc__flip-180' } ` } />
378
- ) }
409
+ { renderIcon ( ) }
410
+ < div
411
+ data-testid = { `add-${ componentType } -button` }
412
+ className = { `flex left lh-20 ${ ! title ? 'fw-5 fs-14 cb-5' : 'fw-5 fs-14 cn-9' } ` }
413
+ >
414
+ { title || `Add ${ componentType === 'secret' ? 'Secret' : 'ConfigMap' } ` }
415
+ { cmSecretStateLabel && < div className = "flex tag ml-12" > { cmSecretStateLabel } </ div > }
379
416
</ div >
380
- ) }
381
- </ article >
382
- { ! collapsed && renderDetails ( ) }
383
- </ section >
417
+ { title && (
418
+ < div className = "flex right" >
419
+ { isProtected && (
420
+ < >
421
+ { renderDraftState ( ) }
422
+ < ProtectedIcon className = "icon-dim-20 mr-10 fcv-5" />
423
+ </ >
424
+ ) }
425
+ { isLoader ? (
426
+ < span style = { { width : '20px' } } >
427
+ < Progressing />
428
+ </ span >
429
+ ) : (
430
+ < Dropdown
431
+ className = { `icon-dim-20 rotate ${ name === title ? 'dc__flip-180' : '' } ` }
432
+ />
433
+ ) }
434
+ </ div >
435
+ ) }
436
+ </ article >
437
+
438
+ { ! isLoader ? renderDetails ( ) : null }
439
+ </ section >
440
+ </ div >
441
+ </ ConditionalWrap >
384
442
</ >
385
443
)
386
444
}
@@ -400,27 +458,30 @@ export function ProtectedConfigMapSecretDetails({
400
458
parentName,
401
459
reloadEnvironments,
402
460
} : ProtectedConfigMapSecretDetailsProps ) {
403
- const { appId, envId } = useParams < { appId ; envId } > ( )
461
+ const { appId, name } = useParams < { appId ; name } > ( )
404
462
const [ isLoader , setLoader ] = useState < boolean > ( false )
405
463
const [ baseData , setBaseData ] = useState ( null )
406
- const [ abortController , setAbortController ] = useState ( new AbortController ( ) ) ;
407
-
464
+ const [ abortController , setAbortController ] = useState ( new AbortController ( ) )
408
465
409
466
const getBaseData = async ( ) => {
410
467
try {
411
468
abortController . abort ( )
412
469
let newAbortController = new AbortController ( )
413
470
setAbortController ( newAbortController )
414
471
setLoader ( true )
415
- const { result } = await ( componentType === 'secret' ? getSecretList ( appId , { signal : newAbortController . signal } ) : getConfigMapList ( appId , { signal : newAbortController . signal } ) )
472
+ const { result } = await ( componentType === 'secret'
473
+ ? getSecretList ( appId , { signal : newAbortController . signal } )
474
+ : getConfigMapList ( appId , { signal : newAbortController . signal } ) )
416
475
let _baseData
417
476
if ( result ?. configData ?. length ) {
418
477
_baseData = result . configData . find ( ( config ) => config . name === data . name )
419
478
if ( _baseData ) {
420
479
_baseData . unAuthorized = data . unAuthorized
421
480
}
422
481
if ( componentType === 'secret' && ! data . unAuthorized ) {
423
- const { result : secretResult } = await getCMSecret ( componentType , result . id , appId , data ?. name , { signal : newAbortController . signal } )
482
+ const { result : secretResult } = await getCMSecret ( componentType , result . id , appId , data ?. name , {
483
+ signal : newAbortController . signal ,
484
+ } )
424
485
if ( secretResult ?. configData ?. length ) {
425
486
_baseData = { ...secretResult . configData [ 0 ] , unAuthorized : false }
426
487
}
@@ -435,7 +496,7 @@ export function ProtectedConfigMapSecretDetails({
435
496
436
497
useEffect ( ( ) => {
437
498
if ( draftData . action === 3 && cmSecretStateLabel === CM_SECRET_STATE . OVERRIDDEN ) {
438
- getBaseData ( )
499
+ getBaseData ( )
439
500
}
440
501
} , [ ] )
441
502
@@ -679,7 +740,10 @@ export function ProtectedConfigMapSecretDetails({
679
740
680
741
export const convertToValidValue = ( k : any ) : string => {
681
742
if ( k !== false && k !== true && k !== '' && ! isNaN ( Number ( k ) ) ) {
682
- return Number ( k ) . toString ( )
743
+ //Note: all long integers & floating values in "double quotes" with spaces will be handled in this check
744
+ // eg val: "123678765678756764\n" or val: "1234.67856756787676\n" or "1276767634.67856\n" to trim down \n
745
+ const replacePattern = / \s / g
746
+ return k . toString ( ) . replace ( replacePattern , '' )
683
747
}
684
748
return k . toString ( )
685
749
}
0 commit comments