diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts index fabab2a887..042e99cb45 100644 --- a/src/lib/db/feature-environment-store.ts +++ b/src/lib/db/feature-environment-store.ts @@ -475,71 +475,81 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { async cloneStrategies( sourceEnvironment: string, destinationEnvironment: string, + projects: string[], ): Promise { const stopTimer = this.timer('cloneStrategies'); - const sourceFeatureStrategies = await this.db( - 'feature_strategies', - ).where({ - environment: sourceEnvironment, - }); - const clonedStrategyRows = sourceFeatureStrategies.map( - (featureStrategy) => { - return { + await this.db.transaction(async (trx) => { + const sourceFeatureStrategies = await trx( + '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( + (featureStrategy) => ({ + ...featureStrategy, id: uuidv4(), - feature_name: featureStrategy.feature_name, - project_name: featureStrategy.project_name, environment: destinationEnvironment, - strategy_name: featureStrategy.strategy_name, parameters: JSON.stringify(featureStrategy.parameters), constraints: JSON.stringify(featureStrategy.constraints), - sort_order: featureStrategy.sort_order, variants: JSON.stringify(featureStrategy.variants), - }; - }, - ); - - if (clonedStrategyRows.length === 0) { - stopTimer(); - return Promise.resolve(); - } - await this.db('feature_strategies').insert(clonedStrategyRows); - - const newStrategyMapping = new Map(); - sourceFeatureStrategies.forEach((sourceStrategy, index) => { - newStrategyMapping.set( - sourceStrategy.id, - clonedStrategyRows[index].id, + }), ); + + await trx('feature_strategies').insert(clonedStrategyRows); + + const newStrategyIdByOld = new Map(); + sourceFeatureStrategies.forEach((s, i) => { + newStrategyIdByOld.set(s.id, clonedStrategyRows[i].id); + }); + + const segmentsToClone = await trx('feature_strategy_segment as fss') + .join( + 'feature_strategies as fs', + 'fss.feature_strategy_id', + 'fs.id', + ) + .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); + + if (segmentsToClone.length) { + const clonedSegmentRows = segmentsToClone + .map((row) => { + const mappedId = newStrategyIdByOld.get( + row.feature_strategy_id, + ); + if (!mappedId) return null; + return { + feature_strategy_id: mappedId, + segment_id: row.segment_id, + }; + }) + .filter( + ( + r, + ): r is { + feature_strategy_id: string; + segment_id: number; + } => Boolean(r), + ); + + if (clonedSegmentRows.length) { + await trx('feature_strategy_segment').insert( + clonedSegmentRows, + ); + } + } }); - const segmentsToClone: ISegmentRow[] = await this.db( - 'feature_strategy_segment as fss', - ) - .select(['id', 'segment_id']) - .join( - 'feature_strategies AS fs', - 'fss.feature_strategy_id', - 'fs.id', - ) - .where('environment', sourceEnvironment); - - const clonedSegmentIdRows = segmentsToClone.map( - (existingSegmentRow) => { - return { - feature_strategy_id: newStrategyMapping.get( - existingSegmentRow.id, - ), - segment_id: existingSegmentRow.segment_id, - }; - }, - ); - - if (clonedSegmentIdRows.length > 0) { - await this.db('feature_strategy_segment').insert( - clonedSegmentIdRows, - ); - } stopTimer(); } diff --git a/src/lib/features/release-plans/release-plan-store.ts b/src/lib/features/release-plans/release-plan-store.ts index a4977e547d..2492a1592f 100644 --- a/src/lib/features/release-plans/release-plan-store.ts +++ b/src/lib/features/release-plans/release-plan-store.ts @@ -370,4 +370,32 @@ export class ReleasePlanStore extends CRUDStore< endTimer(); return present; } + + async getByEnvironmentAndProjects( + environment: string, + projects: string[], + ): Promise { + 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); + } } diff --git a/src/lib/types/stores/feature-environment-store.ts b/src/lib/types/stores/feature-environment-store.ts index 4880446145..3dc3e04800 100644 --- a/src/lib/types/stores/feature-environment-store.ts +++ b/src/lib/types/stores/feature-environment-store.ts @@ -68,6 +68,7 @@ export interface IFeatureEnvironmentStore cloneStrategies( sourceEnvironment: string, destinationEnvironment: string, + projects: string[], ): Promise; addVariantsToFeatureEnvironment( featureName: string, diff --git a/src/test/e2e/stores/feature-environment-store.e2e.test.ts b/src/test/e2e/stores/feature-environment-store.e2e.test.ts index a5965476c8..c1640a096c 100644 --- a/src/test/e2e/stores/feature-environment-store.e2e.test.ts +++ b/src/test/e2e/stores/feature-environment-store.e2e.test.ts @@ -165,7 +165,9 @@ test('Copying strategies also copies strategy variants', async () => { }); await featureEnvironmentStore.connectProject('clone-2', 'default'); - await featureEnvironmentStore.cloneStrategies(envName, 'clone-2'); + await featureEnvironmentStore.cloneStrategies(envName, 'clone-2', [ + 'default', + ]); const clonedStrategy = await featureStrategiesStore.getStrategiesForFeatureEnv(