mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: clone environments (#10755)
https://linear.app/unleash/issue/2-3932/cloned-environments-enable-disabled-strategies-unexpectedly Cloning environments didn't work as expected. This fixes a few of issues: - Disabled strategies remain disabled after cloning - All strategy properties are cloned (including e.g. title) - Strategy cloning respects the selected projects - Release plans and their milestones are now correctly cloned
This commit is contained in:
		
							parent
							
								
									8072bc6706
								
							
						
					
					
						commit
						9948e577ee
					
				| @ -475,71 +475,81 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { | |||||||
|     async cloneStrategies( |     async cloneStrategies( | ||||||
|         sourceEnvironment: string, |         sourceEnvironment: string, | ||||||
|         destinationEnvironment: string, |         destinationEnvironment: string, | ||||||
|  |         projects: string[], | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         const stopTimer = this.timer('cloneStrategies'); |         const stopTimer = this.timer('cloneStrategies'); | ||||||
|         const sourceFeatureStrategies = await this.db( | 
 | ||||||
|             'feature_strategies', |         await this.db.transaction(async (trx) => { | ||||||
|         ).where({ |             const sourceFeatureStrategies = await trx( | ||||||
|             environment: sourceEnvironment, |                 'feature_strategies as fs', | ||||||
|         }); |             ) | ||||||
|  |                 .join('features as f', 'f.name', 'fs.feature_name') | ||||||
|  |                 .select('fs.*') | ||||||
|  |                 .where('fs.environment', sourceEnvironment) | ||||||
|  |                 .whereIn('f.project', projects); | ||||||
|  | 
 | ||||||
|  |             if (sourceFeatureStrategies.length === 0) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             const clonedStrategyRows = sourceFeatureStrategies.map( |             const clonedStrategyRows = sourceFeatureStrategies.map( | ||||||
|             (featureStrategy) => { |                 (featureStrategy) => ({ | ||||||
|                 return { |                     ...featureStrategy, | ||||||
|                     id: uuidv4(), |                     id: uuidv4(), | ||||||
|                     feature_name: featureStrategy.feature_name, |  | ||||||
|                     project_name: featureStrategy.project_name, |  | ||||||
|                     environment: destinationEnvironment, |                     environment: destinationEnvironment, | ||||||
|                     strategy_name: featureStrategy.strategy_name, |  | ||||||
|                     parameters: JSON.stringify(featureStrategy.parameters), |                     parameters: JSON.stringify(featureStrategy.parameters), | ||||||
|                     constraints: JSON.stringify(featureStrategy.constraints), |                     constraints: JSON.stringify(featureStrategy.constraints), | ||||||
|                     sort_order: featureStrategy.sort_order, |  | ||||||
|                     variants: JSON.stringify(featureStrategy.variants), |                     variants: JSON.stringify(featureStrategy.variants), | ||||||
|                 }; |                 }), | ||||||
|             }, |  | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|         if (clonedStrategyRows.length === 0) { |             await trx('feature_strategies').insert(clonedStrategyRows); | ||||||
|             stopTimer(); |  | ||||||
|             return Promise.resolve(); |  | ||||||
|         } |  | ||||||
|         await this.db('feature_strategies').insert(clonedStrategyRows); |  | ||||||
| 
 | 
 | ||||||
|         const newStrategyMapping = new Map(); |             const newStrategyIdByOld = new Map<string, string>(); | ||||||
|         sourceFeatureStrategies.forEach((sourceStrategy, index) => { |             sourceFeatureStrategies.forEach((s, i) => { | ||||||
|             newStrategyMapping.set( |                 newStrategyIdByOld.set(s.id, clonedStrategyRows[i].id); | ||||||
|                 sourceStrategy.id, |  | ||||||
|                 clonedStrategyRows[index].id, |  | ||||||
|             ); |  | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         const segmentsToClone: ISegmentRow[] = await this.db( |             const segmentsToClone = await trx('feature_strategy_segment as fss') | ||||||
|             'feature_strategy_segment as fss', |  | ||||||
|         ) |  | ||||||
|             .select(['id', 'segment_id']) |  | ||||||
|                 .join( |                 .join( | ||||||
|                 'feature_strategies AS fs', |                     'feature_strategies as fs', | ||||||
|                     'fss.feature_strategy_id', |                     'fss.feature_strategy_id', | ||||||
|                     'fs.id', |                     'fs.id', | ||||||
|                 ) |                 ) | ||||||
|             .where('environment', sourceEnvironment); |                 .join('features as f', 'f.name', 'fs.feature_name') | ||||||
|  |                 .select('fss.feature_strategy_id', 'fss.segment_id') | ||||||
|  |                 .where('fs.environment', sourceEnvironment) | ||||||
|  |                 .whereIn('f.project', projects); | ||||||
| 
 | 
 | ||||||
|         const clonedSegmentIdRows = segmentsToClone.map( |             if (segmentsToClone.length) { | ||||||
|             (existingSegmentRow) => { |                 const clonedSegmentRows = segmentsToClone | ||||||
|  |                     .map((row) => { | ||||||
|  |                         const mappedId = newStrategyIdByOld.get( | ||||||
|  |                             row.feature_strategy_id, | ||||||
|  |                         ); | ||||||
|  |                         if (!mappedId) return null; | ||||||
|                         return { |                         return { | ||||||
|                     feature_strategy_id: newStrategyMapping.get( |                             feature_strategy_id: mappedId, | ||||||
|                         existingSegmentRow.id, |                             segment_id: row.segment_id, | ||||||
|                     ), |  | ||||||
|                     segment_id: existingSegmentRow.segment_id, |  | ||||||
|                         }; |                         }; | ||||||
|             }, |                     }) | ||||||
|  |                     .filter( | ||||||
|  |                         ( | ||||||
|  |                             r, | ||||||
|  |                         ): r is { | ||||||
|  |                             feature_strategy_id: string; | ||||||
|  |                             segment_id: number; | ||||||
|  |                         } => Boolean(r), | ||||||
|                     ); |                     ); | ||||||
| 
 | 
 | ||||||
|         if (clonedSegmentIdRows.length > 0) { |                 if (clonedSegmentRows.length) { | ||||||
|             await this.db('feature_strategy_segment').insert( |                     await trx('feature_strategy_segment').insert( | ||||||
|                 clonedSegmentIdRows, |                         clonedSegmentRows, | ||||||
|                     ); |                     ); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         stopTimer(); |         stopTimer(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -370,4 +370,32 @@ export class ReleasePlanStore extends CRUDStore< | |||||||
|         endTimer(); |         endTimer(); | ||||||
|         return present; |         return present; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async getByEnvironmentAndProjects( | ||||||
|  |         environment: string, | ||||||
|  |         projects: string[], | ||||||
|  |     ): Promise<ReleasePlan[]> { | ||||||
|  |         const endTimer = this.timer('getByEnvironmentAndProjects'); | ||||||
|  |         const rows = await this.db(`${this.tableName} AS rpd`) | ||||||
|  |             .join('features AS f', 'f.name', 'rpd.feature_name') | ||||||
|  |             .where('rpd.discriminator', 'plan') | ||||||
|  |             .andWhere('rpd.environment', environment) | ||||||
|  |             .whereIn('f.project', projects) | ||||||
|  |             .leftJoin( | ||||||
|  |                 'milestones AS mi', | ||||||
|  |                 'mi.release_plan_definition_id', | ||||||
|  |                 'rpd.id', | ||||||
|  |             ) | ||||||
|  |             .leftJoin('milestone_strategies AS ms', 'ms.milestone_id', 'mi.id') | ||||||
|  |             .leftJoin( | ||||||
|  |                 'milestone_strategy_segments AS mss', | ||||||
|  |                 'mss.milestone_strategy_id', | ||||||
|  |                 'ms.id', | ||||||
|  |             ) | ||||||
|  |             .orderBy('mi.sort_order', 'asc') | ||||||
|  |             .orderBy('ms.sort_order', 'asc') | ||||||
|  |             .select(selectColumns); | ||||||
|  |         endTimer(); | ||||||
|  |         return processReleasePlanRows(rows); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -68,6 +68,7 @@ export interface IFeatureEnvironmentStore | |||||||
|     cloneStrategies( |     cloneStrategies( | ||||||
|         sourceEnvironment: string, |         sourceEnvironment: string, | ||||||
|         destinationEnvironment: string, |         destinationEnvironment: string, | ||||||
|  |         projects: string[], | ||||||
|     ): Promise<void>; |     ): Promise<void>; | ||||||
|     addVariantsToFeatureEnvironment( |     addVariantsToFeatureEnvironment( | ||||||
|         featureName: string, |         featureName: string, | ||||||
|  | |||||||
| @ -165,7 +165,9 @@ test('Copying strategies also copies strategy variants', async () => { | |||||||
|     }); |     }); | ||||||
|     await featureEnvironmentStore.connectProject('clone-2', 'default'); |     await featureEnvironmentStore.connectProject('clone-2', 'default'); | ||||||
| 
 | 
 | ||||||
|     await featureEnvironmentStore.cloneStrategies(envName, 'clone-2'); |     await featureEnvironmentStore.cloneStrategies(envName, 'clone-2', [ | ||||||
|  |         'default', | ||||||
|  |     ]); | ||||||
| 
 | 
 | ||||||
|     const clonedStrategy = |     const clonedStrategy = | ||||||
|         await featureStrategiesStore.getStrategiesForFeatureEnv( |         await featureStrategiesStore.getStrategiesForFeatureEnv( | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user