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',
|
|
||||||
).where({
|
|
||||||
environment: sourceEnvironment,
|
|
||||||
});
|
|
||||||
|
|
||||||
const clonedStrategyRows = sourceFeatureStrategies.map(
|
await this.db.transaction(async (trx) => {
|
||||||
(featureStrategy) => {
|
const sourceFeatureStrategies = await trx(
|
||||||
return {
|
'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(),
|
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) {
|
|
||||||
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<string, string>();
|
||||||
|
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();
|
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