mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
add support for cloning environments (#2205)
* Add support for cloning environments Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
parent
28880adc2d
commit
0553976240
@ -435,4 +435,17 @@ export class AccessStore implements IAccessStore {
|
||||
})
|
||||
.delete();
|
||||
}
|
||||
|
||||
async cloneEnvironmentPermissions(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void> {
|
||||
return this.db.raw(
|
||||
`insert into role_permission
|
||||
(role_id, permission_id, environment)
|
||||
(select role_id, permission_id, ?
|
||||
from ${T.ROLE_PERMISSION} where environment = ?)`,
|
||||
[destinationEnvironment, sourceEnvironment],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,12 @@ import metricsHelper from '../util/metrics-helper';
|
||||
import { DB_TIME } from '../metric-events';
|
||||
import { IFeatureEnvironment } from '../types/model';
|
||||
import NotFoundError from '../error/notfound-error';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const T = {
|
||||
featureEnvs: 'feature_environments',
|
||||
featureStrategies: 'feature_strategies',
|
||||
features: 'features',
|
||||
};
|
||||
|
||||
interface IFeatureEnvironmentRow {
|
||||
@ -21,6 +23,11 @@ interface IFeatureEnvironmentRow {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface ISegmentRow {
|
||||
id: string;
|
||||
segment_id: number;
|
||||
}
|
||||
|
||||
export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||
private db: Knex;
|
||||
|
||||
@ -268,4 +275,83 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async copyEnvironmentFeaturesByProjects(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
projects: string[],
|
||||
): Promise<void> {
|
||||
await this.db.raw(
|
||||
`INSERT INTO ${T.featureEnvs} (
|
||||
SELECT distinct ? AS environment, feature_name, enabled FROM ${T.featureEnvs} INNER JOIN ${T.features} ON ${T.featureEnvs}.feature_name = ${T.features}.name WHERE environment = ? AND project = ANY(?))`,
|
||||
[destinationEnvironment, sourceEnvironment, projects],
|
||||
);
|
||||
}
|
||||
|
||||
async cloneStrategies(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void> {
|
||||
let sourceFeatureStrategies = await this.db('feature_strategies').where(
|
||||
{
|
||||
environment: sourceEnvironment,
|
||||
},
|
||||
);
|
||||
|
||||
const clonedStrategyRows = sourceFeatureStrategies.map(
|
||||
(featureStrategy) => {
|
||||
return {
|
||||
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,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (clonedStrategyRows.length === 0) {
|
||||
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,
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +246,23 @@ class ProjectStore implements IProjectStore {
|
||||
.ignore();
|
||||
}
|
||||
|
||||
async addEnvironmentToProjects(
|
||||
environment: string,
|
||||
projects: string[],
|
||||
): Promise<void> {
|
||||
const rows = projects.map((project) => {
|
||||
return {
|
||||
project_id: project,
|
||||
environment_name: environment,
|
||||
};
|
||||
});
|
||||
|
||||
await this.db('project_environments')
|
||||
.insert(rows)
|
||||
.onConflict(['project_id', 'environment_name'])
|
||||
.ignore();
|
||||
}
|
||||
|
||||
async getEnvironmentsForProject(id: string): Promise<string[]> {
|
||||
return this.db('project_environments')
|
||||
.where({
|
||||
|
@ -131,6 +131,13 @@ export interface IEnvironmentCreate {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface IEnvironmentClone {
|
||||
name: string;
|
||||
projects?: string[];
|
||||
type: string;
|
||||
clonePermissions?: boolean;
|
||||
}
|
||||
|
||||
export interface IEnvironmentOverview {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
|
@ -127,4 +127,9 @@ export interface IAccessStore extends Store<IRole, number> {
|
||||
permission: string,
|
||||
environment?: string,
|
||||
): Promise<void>;
|
||||
|
||||
cloneEnvironmentPermissions(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
@ -48,4 +48,13 @@ export interface IFeatureEnvironmentStore
|
||||
|
||||
connectProject(environment: string, projectId: string): Promise<void>;
|
||||
disconnectProject(environment: string, projectId: string): Promise<void>;
|
||||
copyEnvironmentFeaturesByProjects(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
projects: string[],
|
||||
): Promise<void>;
|
||||
cloneStrategies(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
@ -47,4 +47,8 @@ export interface IProjectStore extends Store<IProject, string> {
|
||||
getProjectLinksForEnvironments(
|
||||
environments: string[],
|
||||
): Promise<IEnvironmentProjectLink[]>;
|
||||
addEnvironmentToProjects(
|
||||
environment: string,
|
||||
projects: string[],
|
||||
): Promise<void>;
|
||||
}
|
||||
|
7
src/test/fixtures/fake-access-store.ts
vendored
7
src/test/fixtures/fake-access-store.ts
vendored
@ -185,6 +185,13 @@ class AccessStoreMock implements IAccessStore {
|
||||
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
cloneEnvironmentPermissions(
|
||||
sourceEnvironment: string,
|
||||
destinationEnvironment: string,
|
||||
): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AccessStoreMock;
|
||||
|
@ -161,4 +161,24 @@ export default class FakeFeatureEnvironmentStore
|
||||
): Promise<void> {
|
||||
return Promise.reject(new Error('Not implemented'));
|
||||
}
|
||||
|
||||
copyEnvironmentFeaturesByProjects(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
sourceEnvironment: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
destinationEnvironment: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
projects: string[],
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
cloneStrategies(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
sourceEnvironment: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
destinationEnvironment: string,
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
9
src/test/fixtures/fake-project-store.ts
vendored
9
src/test/fixtures/fake-project-store.ts
vendored
@ -141,4 +141,13 @@ export default class FakeProjectStore implements IProjectStore {
|
||||
getProjectsByUser(userId: number): Promise<string[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
addEnvironmentToProjects(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
environment: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
projects: string[],
|
||||
): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user