diff --git a/src/lib/db/environment-store.ts b/src/lib/db/environment-store.ts index 2027636678..2aa45a83b4 100644 --- a/src/lib/db/environment-store.ts +++ b/src/lib/db/environment-store.ts @@ -39,6 +39,17 @@ function mapRow(row: IEnvironmentsTable): IEnvironment { }; } +function fieldToRow(env: IEnvironment): IEnvironmentsTable { + return { + name: env.name, + display_name: env.displayName, + type: env.type, + sort_order: env.sortOrder, + enabled: env.enabled, + protected: env.protected, + }; +} + const TABLE = 'environments'; export default class EnvironmentStore implements IEnvironmentStore { @@ -58,6 +69,18 @@ export default class EnvironmentStore implements IEnvironmentStore { }); } + async importEnvironments( + environments: IEnvironment[], + ): Promise { + const rows = await this.db(TABLE) + .insert(environments.map(fieldToRow)) + .returning(COLUMNS) + .onConflict('name') + .ignore(); + + return rows.map(mapRow); + } + async deleteAll(): Promise { await this.db(TABLE).del(); } diff --git a/src/lib/services/state-service.ts b/src/lib/services/state-service.ts index 63a10ca904..e697fc04ef 100644 --- a/src/lib/services/state-service.ts +++ b/src/lib/services/state-service.ts @@ -1,11 +1,13 @@ import { stateSchema } from './state-schema'; import { + DROP_ENVIRONMENTS, DROP_FEATURE_TAGS, DROP_FEATURES, DROP_PROJECTS, DROP_STRATEGIES, DROP_TAG_TYPES, DROP_TAGS, + ENVIRONMENT_IMPORT, FEATURE_IMPORT, FEATURE_TAG_IMPORT, PROJECT_IMPORT, @@ -151,6 +153,15 @@ export default class StateService { } const importData = await stateSchema.validateAsync(data); + if (importData.environments) { + await this.importEnvironments({ + environments: data.environments, + userName, + dropBeforeImport, + keepExisting, + }); + } + if (importData.features) { let projectData; if (!importData.version || importData.version === 1) { @@ -365,6 +376,42 @@ export default class StateService { ); } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + async importEnvironments({ + environments, + userName, + dropBeforeImport, + keepExisting, + }): Promise { + this.logger.info(`Import ${environments.length} projects`); + const oldEnvs = dropBeforeImport + ? [] + : await this.environmentStore.getAll(); + if (dropBeforeImport) { + this.logger.info('Dropping existing environments'); + await this.environmentStore.deleteAll(); + await this.eventStore.store({ + type: DROP_ENVIRONMENTS, + createdBy: userName, + data: { name: 'all-projects' }, + }); + } + const envsImport = environments.filter((env) => + keepExisting ? !oldEnvs.some((old) => old.name === env.name) : true, + ); + if (envsImport.length > 0) { + const importedEnvs = await this.environmentStore.importEnvironments( + envsImport, + ); + const importedEnvironmentEvents = importedEnvs.map((env) => ({ + type: ENVIRONMENT_IMPORT, + createdBy: userName, + data: env, + })); + await this.eventStore.batchStore(importedEnvironmentEvents); + } + } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async importProjects({ projects, diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts index 5facf26d35..b50a96efe6 100644 --- a/src/lib/types/events.ts +++ b/src/lib/types/events.ts @@ -48,3 +48,5 @@ export const DB_POOL_UPDATE = 'db-pool-update'; export const USER_CREATED = 'user-created'; export const USER_UPDATED = 'user-updated'; export const USER_DELETED = 'user-deleted'; +export const DROP_ENVIRONMENTS = 'drop-environments'; +export const ENVIRONMENT_IMPORT = 'environment-import'; diff --git a/src/lib/types/stores/environment-store.ts b/src/lib/types/stores/environment-store.ts index 9eb7dc6b12..cfc4ba609a 100644 --- a/src/lib/types/stores/environment-store.ts +++ b/src/lib/types/stores/environment-store.ts @@ -14,4 +14,5 @@ export interface IEnvironmentStore extends Store { value: string | number | boolean, ): Promise; updateSortOrder(id: string, value: number): Promise; + importEnvironments(environments: IEnvironment[]): Promise; } diff --git a/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts b/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts index b987f95524..4b8552054d 100644 --- a/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts +++ b/src/test/e2e/api/admin/project/feature.strategy.e2e.test.ts @@ -1,7 +1,6 @@ import dbInit, { ITestDb } from '../../../helpers/database-init'; import { IUnleashTest, setupApp } from '../../../helpers/test-helper'; import getLogger from '../../../../fixtures/no-logger'; -import { GLOBAL_ENV } from '../../../../../lib/types/environment'; import { DEFAULT_ENV } from '../../../../../lib/util/constants'; let app: IUnleashTest; @@ -645,7 +644,7 @@ test('Environments are returned in sortOrder', async () => { .expect(200) .expect((res) => { expect(res.body.environments).toHaveLength(3); - expect(res.body.environments[0].name).toBe(GLOBAL_ENV); + expect(res.body.environments[0].name).toBe(DEFAULT_ENV); expect(res.body.environments[1].name).toBe(sortedSecond); expect(res.body.environments[2].name).toBe(sortedLast); }); diff --git a/src/test/fixtures/fake-environment-store.ts b/src/test/fixtures/fake-environment-store.ts index f5422fa20c..a939f5ec30 100644 --- a/src/test/fixtures/fake-environment-store.ts +++ b/src/test/fixtures/fake-environment-store.ts @@ -3,6 +3,11 @@ import NotFoundError from '../../lib/error/notfound-error'; import { IEnvironmentStore } from '../../lib/types/stores/environment-store'; export default class FakeEnvironmentStore implements IEnvironmentStore { + importEnvironments(envs: IEnvironment[]): Promise { + this.environments = envs; + return Promise.resolve(envs); + } + environments: IEnvironment[] = []; async getAll(): Promise {