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();
|
.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 { DB_TIME } from '../metric-events';
|
||||||
import { IFeatureEnvironment } from '../types/model';
|
import { IFeatureEnvironment } from '../types/model';
|
||||||
import NotFoundError from '../error/notfound-error';
|
import NotFoundError from '../error/notfound-error';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
featureEnvs: 'feature_environments',
|
featureEnvs: 'feature_environments',
|
||||||
featureStrategies: 'feature_strategies',
|
featureStrategies: 'feature_strategies',
|
||||||
|
features: 'features',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IFeatureEnvironmentRow {
|
interface IFeatureEnvironmentRow {
|
||||||
@ -21,6 +23,11 @@ interface IFeatureEnvironmentRow {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ISegmentRow {
|
||||||
|
id: string;
|
||||||
|
segment_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
|
||||||
private db: Knex;
|
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();
|
.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[]> {
|
async getEnvironmentsForProject(id: string): Promise<string[]> {
|
||||||
return this.db('project_environments')
|
return this.db('project_environments')
|
||||||
.where({
|
.where({
|
||||||
|
@ -131,6 +131,13 @@ export interface IEnvironmentCreate {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IEnvironmentClone {
|
||||||
|
name: string;
|
||||||
|
projects?: string[];
|
||||||
|
type: string;
|
||||||
|
clonePermissions?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IEnvironmentOverview {
|
export interface IEnvironmentOverview {
|
||||||
name: string;
|
name: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
@ -127,4 +127,9 @@ export interface IAccessStore extends Store<IRole, number> {
|
|||||||
permission: string,
|
permission: string,
|
||||||
environment?: string,
|
environment?: string,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
cloneEnvironmentPermissions(
|
||||||
|
sourceEnvironment: string,
|
||||||
|
destinationEnvironment: string,
|
||||||
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,13 @@ export interface IFeatureEnvironmentStore
|
|||||||
|
|
||||||
connectProject(environment: string, projectId: string): Promise<void>;
|
connectProject(environment: string, projectId: string): Promise<void>;
|
||||||
disconnectProject(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(
|
getProjectLinksForEnvironments(
|
||||||
environments: string[],
|
environments: string[],
|
||||||
): Promise<IEnvironmentProjectLink[]>;
|
): 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> {
|
removeRolesOfTypeForUser(userId: number, roleType: string): Promise<void> {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneEnvironmentPermissions(
|
||||||
|
sourceEnvironment: string,
|
||||||
|
destinationEnvironment: string,
|
||||||
|
): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AccessStoreMock;
|
module.exports = AccessStoreMock;
|
||||||
|
@ -161,4 +161,24 @@ export default class FakeFeatureEnvironmentStore
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return Promise.reject(new Error('Not implemented'));
|
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[]> {
|
getProjectsByUser(userId: number): Promise<string[]> {
|
||||||
throw new Error('Method not implemented.');
|
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