mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
feat: project environments include visible property (#9427)
This commit is contained in:
parent
ae0fc86e99
commit
e7ac42080d
@ -19,8 +19,6 @@ const getStatusCode = (errorName: string): number => {
|
||||
return 400;
|
||||
case 'PasswordUndefinedError':
|
||||
return 400;
|
||||
case 'MinimumOneEnvironmentError':
|
||||
return 400;
|
||||
case 'InvalidTokenError':
|
||||
return 401;
|
||||
case 'UsedTokenError':
|
||||
|
@ -5,7 +5,6 @@ import FeatureHasTagError from './feature-has-tag-error';
|
||||
import IncompatibleProjectError from './incompatible-project-error';
|
||||
import InvalidOperationError from './invalid-operation-error';
|
||||
import InvalidTokenError from './invalid-token-error';
|
||||
import MinimumOneEnvironmentError from './minimum-one-environment-error';
|
||||
import NameExistsError from './name-exists-error';
|
||||
import PermissionError from './permission-error';
|
||||
import { OperationDeniedError } from './operation-denied-error';
|
||||
@ -27,7 +26,6 @@ export {
|
||||
IncompatibleProjectError,
|
||||
InvalidOperationError,
|
||||
InvalidTokenError,
|
||||
MinimumOneEnvironmentError,
|
||||
NameExistsError,
|
||||
PermissionError,
|
||||
ForbiddenError,
|
||||
|
@ -8,7 +8,6 @@ export const UnleashApiErrorTypes = [
|
||||
'IncompatibleProjectError',
|
||||
'InvalidOperationError',
|
||||
'InvalidTokenError',
|
||||
'MinimumOneEnvironmentError',
|
||||
'NameExistsError',
|
||||
'NoAccessError',
|
||||
'NotFoundError',
|
||||
|
@ -338,3 +338,54 @@ test('Override works correctly when enabling default and disabling prod and dev'
|
||||
expect(targetedEnvironment?.enabled).toBe(true);
|
||||
expect(allOtherEnvironments.every((x) => !x)).toBe(true);
|
||||
});
|
||||
|
||||
test('getProjectEnvironments also includes whether or not a given project is visible on a given environment', async () => {
|
||||
const assertContains = (environments, envName, visible) => {
|
||||
const env = environments.find((e) => e.name === envName);
|
||||
expect(env).toBeDefined();
|
||||
expect(env.visible).toBe(visible);
|
||||
};
|
||||
|
||||
const assertContainsVisible = (environments, envName) => {
|
||||
assertContains(environments, envName, true);
|
||||
};
|
||||
|
||||
const assertContainsNotVisible = (environments, envName) => {
|
||||
assertContains(environments, envName, false);
|
||||
};
|
||||
|
||||
const projectId = 'default';
|
||||
const firstEnvTest = 'some-connected-environment';
|
||||
const secondEnvTest = 'some-also-connected-environment';
|
||||
await db.stores.environmentStore.create({
|
||||
name: firstEnvTest,
|
||||
type: 'production',
|
||||
});
|
||||
await db.stores.environmentStore.create({
|
||||
name: secondEnvTest,
|
||||
type: 'production',
|
||||
});
|
||||
|
||||
await service.addEnvironmentToProject(
|
||||
firstEnvTest,
|
||||
projectId,
|
||||
SYSTEM_USER_AUDIT,
|
||||
);
|
||||
await service.addEnvironmentToProject(
|
||||
secondEnvTest,
|
||||
projectId,
|
||||
SYSTEM_USER_AUDIT,
|
||||
);
|
||||
let environments = await service.getProjectEnvironments(projectId);
|
||||
assertContainsVisible(environments, firstEnvTest);
|
||||
assertContainsVisible(environments, secondEnvTest);
|
||||
|
||||
await service.removeEnvironmentFromProject(
|
||||
firstEnvTest,
|
||||
projectId,
|
||||
SYSTEM_USER_AUDIT,
|
||||
);
|
||||
environments = await service.getProjectEnvironments(projectId);
|
||||
assertContainsNotVisible(environments, firstEnvTest);
|
||||
assertContainsVisible(environments, secondEnvTest);
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
type IEnvironmentStore,
|
||||
type IFeatureEnvironmentStore,
|
||||
type IFeatureStrategiesStore,
|
||||
type IProjectEnvironment,
|
||||
type IProjectsAvailableOnEnvironment,
|
||||
type ISortOrder,
|
||||
type IUnleashConfig,
|
||||
type IUnleashStores,
|
||||
@ -19,7 +19,6 @@ import NameExistsError from '../../error/name-exists-error';
|
||||
import { sortOrderSchema } from '../../services/sort-order-schema';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import type { IProjectStore } from '../../features/project/project-store-type';
|
||||
import MinimumOneEnvironmentError from '../../error/minimum-one-environment-error';
|
||||
import type { IFlagResolver } from '../../types/experimental';
|
||||
import type { CreateFeatureStrategySchema } from '../../openapi';
|
||||
import type EventService from '../events/event-service';
|
||||
@ -77,8 +76,24 @@ export default class EnvironmentService {
|
||||
|
||||
async getProjectEnvironments(
|
||||
projectId: string,
|
||||
): Promise<IProjectEnvironment[]> {
|
||||
return this.environmentStore.getProjectEnvironments(projectId);
|
||||
): Promise<IProjectsAvailableOnEnvironment[]> {
|
||||
// This function produces an object for every environment, in that object is a boolean
|
||||
// describing whether or not that environment is enabled - aka not deprecated
|
||||
const environments =
|
||||
await this.projectStore.getEnvironmentsForProject(projectId);
|
||||
const environmentsOnProject = new Set(
|
||||
environments.map((env) => env.environment),
|
||||
);
|
||||
|
||||
const allEnvironments =
|
||||
await this.environmentStore.getProjectEnvironments(projectId);
|
||||
|
||||
return allEnvironments.map((env) => {
|
||||
return {
|
||||
...env,
|
||||
visible: environmentsOnProject.has(env.name),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async updateSortOrder(sortOrder: ISortOrder): Promise<void> {
|
||||
@ -254,22 +269,13 @@ export default class EnvironmentService {
|
||||
const projectEnvs =
|
||||
await this.projectStore.getEnvironmentsForProject(projectId);
|
||||
|
||||
if (projectEnvs.length > 1) {
|
||||
await this.forceRemoveEnvironmentFromProject(
|
||||
await this.forceRemoveEnvironmentFromProject(environment, projectId);
|
||||
await this.eventService.storeEvent(
|
||||
new ProjectEnvironmentRemoved({
|
||||
project: projectId,
|
||||
environment,
|
||||
projectId,
|
||||
);
|
||||
await this.eventService.storeEvent(
|
||||
new ProjectEnvironmentRemoved({
|
||||
project: projectId,
|
||||
environment,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new MinimumOneEnvironmentError(
|
||||
'You must always have one active environment',
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -98,22 +98,6 @@ test('Should remove environment from project', async () => {
|
||||
expect(envs).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('Should not remove environment from project if project only has one environment enabled', async () => {
|
||||
await app.request
|
||||
.delete(`/api/admin/projects/default/environments/default`)
|
||||
.expect(400)
|
||||
.expect((r) => {
|
||||
expect(r.body.details[0].message).toBe(
|
||||
'You must always have one active environment',
|
||||
);
|
||||
});
|
||||
|
||||
const envs =
|
||||
await db.stores.projectStore.getEnvironmentsForProject('default');
|
||||
|
||||
expect(envs).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('Should add default strategy to environment', async () => {
|
||||
const defaultStrategy = {
|
||||
name: 'flexibleRollout',
|
||||
|
@ -1,4 +1,7 @@
|
||||
import type { IEnvironment, IProjectEnvironment } from '../../types/model';
|
||||
import type {
|
||||
IEnvironment,
|
||||
IProjectsAvailableOnEnvironment,
|
||||
} from '../../types/model';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import type { IEnvironmentStore } from './environment-store-type';
|
||||
|
||||
@ -140,7 +143,7 @@ export default class FakeEnvironmentStore implements IEnvironmentStore {
|
||||
async getProjectEnvironments(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
projectId: string,
|
||||
): Promise<IProjectEnvironment[]> {
|
||||
): Promise<IProjectsAvailableOnEnvironment[]> {
|
||||
return Promise.reject(new Error('Not implemented'));
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,12 @@ export const environmentProjectSchema = {
|
||||
'The strategy configuration to add when enabling a feature environment by default',
|
||||
$ref: '#/components/schemas/createFeatureStrategySchema',
|
||||
},
|
||||
visible: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description:
|
||||
'Indicates whether the environment can be enabled for feature flags in the project',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
|
@ -212,6 +212,10 @@ export interface IProjectEnvironment extends IEnvironment {
|
||||
defaultStrategy?: CreateFeatureStrategySchema;
|
||||
}
|
||||
|
||||
export interface IProjectsAvailableOnEnvironment extends IProjectEnvironment {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export interface IEnvironmentCreate {
|
||||
name: string;
|
||||
type: string;
|
||||
|
Loading…
Reference in New Issue
Block a user