@@ -13,6 +13,7 @@ import {
1313 mockGetDatabaseEngineConfigs ,
1414 mockGetDatabaseTypes ,
1515 mockUpdateDatabase ,
16+ mockUpdateDatabaseError ,
1617} from 'support/intercepts/databases' ;
1718import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags' ;
1819import { ui } from 'support/ui' ;
@@ -40,15 +41,45 @@ import type { DatabaseClusterConfiguration } from 'support/constants/databases';
4041 */
4142const getFlattenDefaultConfigs = (
4243 engineConfig : Record < string , any > ,
43- prefix = ''
44+ prefix = '' ,
45+ includePrefix = true
4446) : string [ ] =>
4547 Object . entries ( engineConfig ) . flatMap ( ( [ key , value ] ) => {
4648 const fullKey = prefix ? `${ prefix } .${ key } ` : key ;
4749 return typeof value === 'object' && value !== null && ! Array . isArray ( value )
48- ? getFlattenDefaultConfigs ( value , fullKey )
49- : [ fullKey ] ;
50+ ? getFlattenDefaultConfigs (
51+ value ,
52+ includePrefix ? fullKey : '' ,
53+ includePrefix
54+ )
55+ : [ includePrefix ? fullKey : key ] ;
5056 } ) ;
5157
58+ const flattenConfigsEngineLevel = (
59+ configs : Record < string , any >
60+ ) : Record < string , any > => {
61+ const result : Record < string , any > = { } ;
62+ Object . entries ( configs ) . forEach ( ( [ key , value ] ) => {
63+ if (
64+ typeof value === 'object' &&
65+ value !== null &&
66+ // Only flatten if value is a config group (not a config leaf)
67+ Object . values ( value ) . every (
68+ ( v ) => typeof v === 'object' && v !== null && ! Array . isArray ( v )
69+ )
70+ ) {
71+ // Nested group (e.g., pg, mysql)
72+ Object . entries ( value ) . forEach ( ( [ subKey , subValue ] ) => {
73+ result [ subKey ] = subValue ;
74+ } ) ;
75+ } else {
76+ // Top-level config
77+ result [ key ] = value ;
78+ }
79+ } ) ;
80+ return result ;
81+ } ;
82+
5283/**
5384 * Get list of advanced Configurations available for users to add/modify
5485 *
@@ -100,9 +131,14 @@ const addConfigsToUI = (
100131 ? false
101132 : value . example ;
102133
134+ // Get all existing config keys from engine_config (handles nested structures)
135+ const existingConfigKeys = new Set (
136+ getFlattenDefaultConfigs ( database . engine_config , '' , false )
137+ ) ;
138+
103139 // Process new configs to be added
104140 const newEntries = Object . entries ( configsList )
105- . filter ( ( [ key ] ) => ! database . engine_config [ engineType ] [ key ] )
141+ . filter ( ( [ key ] ) => ! existingConfigKeys . has ( key ) )
106142 . slice ( 0 , addSingle ? 1 : undefined ) ; // Limit to 1 if addSingle, otherwise all
107143
108144 if ( newEntries . length > 0 ) {
@@ -121,8 +157,20 @@ const addConfigsToUI = (
121157 . within ( ( ) => {
122158 // Confirms configure drawer already renders default configs
123159 Object . keys ( database . engine_config [ engineType ] ) . forEach ( ( key ) => {
160+ cy . findByText ( `${ engineType } .${ key } ` ) . scrollIntoView ( ) ;
124161 cy . findByText ( `${ engineType } .${ key } ` ) . should ( 'be.visible' ) ;
125162 } ) ;
163+ Object . keys ( database . engine_config )
164+ . filter (
165+ ( key ) =>
166+ key !== 'pg' &&
167+ key !== 'mysql' &&
168+ typeof database . engine_config [ key ] !== 'object'
169+ )
170+ . forEach ( ( key ) => {
171+ cy . findByText ( key ) . scrollIntoView ( ) ;
172+ cy . findByText ( key ) . should ( 'be.visible' ) ;
173+ } ) ;
126174
127175 // Adding configs one at a time from the dropdown
128176 cy . get (
@@ -140,9 +188,24 @@ const addConfigsToUI = (
140188
141189 // Type value for non-boolean configs
142190 if ( value . type !== 'boolean' ) {
143- cy . get ( `[name="${ flatKey } "]` ) . scrollIntoView ( ) ;
144- cy . get ( `[name="${ flatKey } "]` ) . should ( 'be.visible' ) . clear ( ) ;
145- cy . get ( `[name="${ flatKey } "]` ) . type ( additionalConfigs [ flatKey ] ) ;
191+ if ( value . enum ) {
192+ cy . findByText ( flatKey ) . scrollIntoView ( ) ;
193+ cy . findByText ( flatKey )
194+ . parent ( )
195+ . within ( ( ) => {
196+ ui . autocomplete . find ( ) . click ( ) ;
197+ ui . autocomplete . find ( ) . clear ( ) ;
198+ ui . autocomplete . find ( ) . type ( `${ additionalConfigs [ flatKey ] } ` ) ;
199+ } ) ;
200+ ui . autocompletePopper
201+ . findByTitle ( `${ additionalConfigs [ flatKey ] } ` )
202+ . click ( ) ;
203+ } else {
204+ cy . get ( `[name="${ flatKey } "]` ) . scrollIntoView ( ) ;
205+ cy . get ( `[name="${ flatKey } "]` ) . should ( 'be.visible' ) ;
206+ cy . get ( `[name="${ flatKey } "]` ) . clear ( ) ;
207+ cy . get ( `[name="${ flatKey } "]` ) . type ( additionalConfigs [ flatKey ] ) ;
208+ }
146209 }
147210 } ) ;
148211 } ) ;
@@ -195,7 +258,7 @@ describe('Update database clusters', () => {
195258 ) ;
196259
197260 mockGetAccount ( accountFactory . build ( ) ) . as ( 'getAccount' ) ;
198- mockGetDatabase ( database ) . as ( 'getDatabase' ) . debug ( ) ;
261+ mockGetDatabase ( database ) . as ( 'getDatabase' ) ;
199262 mockGetDatabaseTypes ( mockDatabaseNodeTypes ) . as ( 'getDatabaseTypes' ) ;
200263 mockGetDatabaseEngineConfigs ( database . engine , mockConfigs ) ;
201264
@@ -215,6 +278,7 @@ describe('Update database clusters', () => {
215278 } ) ;
216279
217280 // Confirms all the buttons are in the initial state - enabled/disabled
281+ ui . cdsButton . findButtonByTitle ( 'Configure' ) . scrollIntoView ( ) ;
218282 ui . cdsButton
219283 . findButtonByTitle ( 'Configure' )
220284 . should ( 'be.visible' )
@@ -226,18 +290,15 @@ describe('Update database clusters', () => {
226290 . findButtonByTitle ( 'Add' )
227291 . should ( 'exist' )
228292 . should ( 'be.disabled' ) ;
229- ui . button
230- . findByTitle ( 'Save' )
231- . scrollIntoView ( )
232- . should ( 'be.visible' )
233- . should ( 'be.disabled' ) ;
293+ ui . button . findByTitle ( 'Save' ) . should ( 'exist' ) . should ( 'be.disabled' ) ;
234294
235295 ui . button
236296 . findByTitle ( 'Cancel' )
237- . scrollIntoView ( )
238- . should ( 'be.visible' )
297+ . should ( 'exist' )
239298 . should ( 'be.enabled' )
240- . click ( ) ;
299+ . then ( ( btn ) => {
300+ btn [ 0 ] . click ( ) ;
301+ } ) ;
241302
242303 ui . cdsButton
243304 . findButtonByTitle ( 'Configure' )
@@ -247,9 +308,11 @@ describe('Update database clusters', () => {
247308
248309 ui . drawer . findByTitle ( 'Advanced Configuration' ) . should ( 'be.visible' ) ;
249310 cy . get ( '[aria-label="Close drawer"]' )
250- . should ( 'be.visible ' )
311+ . should ( 'exist ' )
251312 . should ( 'be.enabled' )
252- . click ( ) ;
313+ . then ( ( btn ) => {
314+ btn [ 0 ] . click ( ) ;
315+ } ) ;
253316 } ) ;
254317
255318 /*
@@ -296,6 +359,7 @@ describe('Update database clusters', () => {
296359 cy . wait ( [ '@getDatabase' , '@getDatabaseTypes' ] ) ;
297360
298361 // Expand configure drawer to add configs
362+ ui . cdsButton . findButtonByTitle ( 'Configure' ) . scrollIntoView ( ) ;
299363 ui . cdsButton
300364 . findButtonByTitle ( 'Configure' )
301365 . should ( 'be.visible' )
@@ -313,31 +377,52 @@ describe('Update database clusters', () => {
313377 true
314378 ) ;
315379
380+ const isSyncReplicationQuorum =
381+ singleConfig [ 'synchronous_replication' ] === 'quorum' ;
382+ const isInvaliClusterSize =
383+ database . cluster_size < 3 && isSyncReplicationQuorum ;
384+
316385 // Update advanced configurations with the newly added config
317- mockUpdateDatabase ( database . id , database . engine , {
318- ...database ,
319- engine_config : {
320- ...( database . engine_config as ConfigCategoryValues ) ,
321- [ engineType ] : {
322- ...( existingConfig as ConfigCategoryValues ) ,
323- ...singleConfig ,
386+ if ( isInvaliClusterSize ) {
387+ mockUpdateDatabaseError (
388+ database . id ,
389+ database . engine ,
390+ 'engine_config.synchronous_replication' ,
391+ 'synchronous_replication is only supported for clusters with 3 nodes'
392+ ) . as ( 'updateAdvancedConfiguration' ) ;
393+ } else {
394+ mockUpdateDatabase ( database . id , database . engine , {
395+ ...database ,
396+ engine_config : {
397+ ...( database . engine_config as ConfigCategoryValues ) ,
398+ [ engineType ] : {
399+ ...( existingConfig as ConfigCategoryValues ) ,
400+ ...singleConfig ,
401+ } ,
324402 } ,
325- } ,
326- } ) . as ( 'updateAdvancedConfiguration' ) ;
327-
403+ } ) . as ( 'updateAdvancedConfiguration' ) ;
404+ }
328405 // Save or Save and Restart Services as per the config added
329406 ui . button
330407 . findByTitle ( saveRestartButton )
331- . scrollIntoView ( )
332- . should ( 'be.visible' )
408+ . should ( 'exist' )
333409 . should ( 'be.enabled' )
334- . click ( ) ;
410+ . then ( ( btn ) => {
411+ btn [ 0 ] . click ( ) ;
412+ } ) ;
335413 cy . wait ( '@updateAdvancedConfiguration' ) ;
336414
337- // Confirms newly added advacned Config on the Configuration tab tableview
338- cy . findByText ( `${ engineType } .${ Object . keys ( singleConfig ) [ 0 ] } ` ) . should (
339- 'be.visible'
340- ) ;
415+ if ( isInvaliClusterSize ) {
416+ // Verify error message is displayed for invalid synchronous replication
417+ cy . findByText (
418+ / s y n c h r o n o u s _ r e p l i c a t i o n i s o n l y s u p p o r t e d f o r c l u s t e r s w i t h 3 n o d e s / i
419+ ) . should ( 'be.visible' ) ;
420+ } else {
421+ // Confirms newly added advanced Config on the Configuration tab tableview
422+ cy . findByText (
423+ `${ engineType } .${ Object . keys ( singleConfig ) [ 0 ] } `
424+ ) . should ( 'be.visible' ) ;
425+ }
341426 } ) ;
342427
343428 /*
@@ -384,48 +469,83 @@ describe('Update database clusters', () => {
384469 cy . wait ( [ '@getDatabase' , '@getDatabaseTypes' ] ) ;
385470
386471 // Expand configure drawer to add configs
472+ ui . cdsButton . findButtonByTitle ( 'Configure' ) . scrollIntoView ( ) ;
387473 ui . cdsButton
388474 . findButtonByTitle ( 'Configure' )
389475 . should ( 'be.visible' )
390476 . should ( 'be.enabled' )
391477 . click ( ) ;
392478
479+ const flatMockConfigs = flattenConfigsEngineLevel ( mockConfigs ) ;
480+
393481 // Add configs from the configList to the existing database cluster
394482 const {
395483 additionalConfigs : allConfig ,
396484 saveButton : saveRestartButton ,
397- } = addConfigsToUI (
398- mockConfigs [ engineType ] ,
399- database ,
400- engineType ,
401- false
402- ) ;
485+ } = addConfigsToUI ( flatMockConfigs , database , engineType , false ) ;
486+
487+ const nestedConfig : Record < string , any > = { } ;
488+ const topLevelConfig : Record < string , any > = { } ;
489+ // Separate nested engine configs and top-level configs
490+ Object . entries ( allConfig ) . forEach ( ( [ key , value ] ) => {
491+ if ( key in mockConfigs [ engineType ] ) {
492+ nestedConfig [ key ] = value ;
493+ } else {
494+ topLevelConfig [ key ] = value ;
495+ }
496+ } ) ;
497+
498+ const isSyncReplicationQuorum =
499+ allConfig [ 'synchronous_replication' ] === 'quorum' ;
500+ const isInvalidClusterSize =
501+ database . cluster_size < 3 && isSyncReplicationQuorum ;
403502
404503 // Update advanced configurations with the newly added config
405- mockUpdateDatabase ( database . id , database . engine , {
406- ...database ,
407- engine_config : {
408- ...( database . engine_config as ConfigCategoryValues ) ,
409- [ engineType ] : {
410- ...( existingConfig as ConfigCategoryValues ) ,
411- ...allConfig ,
504+ if ( isInvalidClusterSize ) {
505+ mockUpdateDatabaseError (
506+ database . id ,
507+ database . engine ,
508+ 'engine_config.synchronous_replication' ,
509+ 'synchronous_replication is only supported for clusters with 3 nodes'
510+ ) . as ( 'updateAdvancedConfiguration' ) ;
511+ } else {
512+ mockUpdateDatabase ( database . id , database . engine , {
513+ ...database ,
514+ engine_config : {
515+ ...( database . engine_config as ConfigCategoryValues ) ,
516+ [ engineType ] : {
517+ ...( existingConfig as ConfigCategoryValues ) ,
518+ ...nestedConfig ,
519+ } ,
520+ ...topLevelConfig ,
412521 } ,
413- } ,
414- } ) . as ( 'updateAdvancedConfiguration' ) ;
522+ } ) . as ( 'updateAdvancedConfiguration' ) ;
523+ }
415524
416525 // Save or Save and Restart Services as per the config added
417526 ui . button
418527 . findByTitle ( saveRestartButton )
419- . scrollIntoView ( )
420- . should ( 'be.visible' )
528+ . should ( 'exist' )
421529 . should ( 'be.enabled' )
422- . click ( ) ;
530+ . then ( ( btn ) => {
531+ btn [ 0 ] . click ( ) ;
532+ } ) ;
423533 cy . wait ( '@updateAdvancedConfiguration' ) ;
424534
425- // Confirms newly added advacned Config on the Configuration tab tableview
426- Object . keys ( allConfig ) . forEach ( ( key ) => {
427- cy . findByText ( `${ engineType } .${ key } ` ) . should ( 'be.visible' ) ;
428- } ) ;
535+ if ( isInvalidClusterSize ) {
536+ // Verify error message is displayed for invalid synchronous replication
537+ cy . findByText (
538+ / s y n c h r o n o u s _ r e p l i c a t i o n i s o n l y s u p p o r t e d f o r c l u s t e r s w i t h 3 n o d e s / i
539+ ) . should ( 'be.visible' ) ;
540+ } else {
541+ // Confirms newly added advanced Config on the Configuration tab tableview
542+ Object . keys ( nestedConfig ) . forEach ( ( key ) => {
543+ cy . findByText ( `${ engineType } .${ key } ` ) . should ( 'be.visible' ) ;
544+ } ) ;
545+ Object . keys ( topLevelConfig ) . forEach ( ( key ) => {
546+ cy . findByText ( `${ key } ` ) . should ( 'be.visible' ) ;
547+ } ) ;
548+ }
429549 } ) ;
430550
431551 /*
@@ -469,6 +589,7 @@ describe('Update database clusters', () => {
469589 cy . wait ( [ '@getDatabase' , '@getDatabaseTypes' ] ) ;
470590
471591 // Expand configure drawer to add configs
592+ ui . cdsButton . findButtonByTitle ( 'Configure' ) . scrollIntoView ( ) ;
472593 ui . cdsButton
473594 . findButtonByTitle ( 'Configure' )
474595 . should ( 'be.visible' )
0 commit comments