mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: Protect archive feature (#5003)
This commit is contained in:
parent
2754c26f2e
commit
cfcf9de65a
@ -182,7 +182,7 @@ export class DependentFeaturesService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
user: string,
|
user: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.dependentFeaturesStore.deleteAll(feature);
|
await this.dependentFeaturesStore.deleteAll([feature]);
|
||||||
await this.eventService.storeEvent({
|
await this.eventService.storeEvent({
|
||||||
type: 'feature-dependencies-removed',
|
type: 'feature-dependencies-removed',
|
||||||
project: projectId,
|
project: projectId,
|
||||||
|
@ -3,5 +3,5 @@ import { FeatureDependency, FeatureDependencyId } from './dependent-features';
|
|||||||
export interface IDependentFeaturesStore {
|
export interface IDependentFeaturesStore {
|
||||||
upsert(featureDependency: FeatureDependency): Promise<void>;
|
upsert(featureDependency: FeatureDependency): Promise<void>;
|
||||||
delete(dependency: FeatureDependencyId): Promise<void>;
|
delete(dependency: FeatureDependencyId): Promise<void>;
|
||||||
deleteAll(child?: string): Promise<void>;
|
deleteAll(children?: string[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,10 @@ export class DependentFeaturesStore implements IDependentFeaturesStore {
|
|||||||
.del();
|
.del();
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAll(feature: string | undefined): Promise<void> {
|
async deleteAll(features: string[] | undefined): Promise<void> {
|
||||||
if (feature) {
|
if (features) {
|
||||||
await this.db('dependent_features')
|
await this.db('dependent_features')
|
||||||
.andWhere('child', feature)
|
.whereIn('child', features)
|
||||||
.del();
|
.del();
|
||||||
} else {
|
} else {
|
||||||
await this.db('dependent_features').del();
|
await this.db('dependent_features').del();
|
||||||
|
@ -777,10 +777,9 @@ export default class ProjectFeaturesController extends Controller {
|
|||||||
res: Response<void>,
|
res: Response<void>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { featureName, projectId } = req.params;
|
const { featureName, projectId } = req.params;
|
||||||
const userName = extractUsername(req);
|
|
||||||
await this.featureService.archiveToggle(
|
await this.featureService.archiveToggle(
|
||||||
featureName,
|
featureName,
|
||||||
userName,
|
req.user,
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
res.status(202).send();
|
res.status(202).send();
|
||||||
|
@ -71,6 +71,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
DATE_OPERATORS,
|
DATE_OPERATORS,
|
||||||
DEFAULT_ENV,
|
DEFAULT_ENV,
|
||||||
|
extractUsernameFromUser,
|
||||||
NUM_OPERATORS,
|
NUM_OPERATORS,
|
||||||
SEMVER_OPERATORS,
|
SEMVER_OPERATORS,
|
||||||
STRING_OPERATORS,
|
STRING_OPERATORS,
|
||||||
@ -1465,6 +1466,25 @@ class FeatureToggleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async archiveToggle(
|
async archiveToggle(
|
||||||
|
featureName: string,
|
||||||
|
user: User,
|
||||||
|
projectId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (projectId) {
|
||||||
|
await this.stopWhenChangeRequestsEnabled(
|
||||||
|
projectId,
|
||||||
|
undefined,
|
||||||
|
user,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.unprotectedArchiveToggle(
|
||||||
|
featureName,
|
||||||
|
extractUsernameFromUser(user),
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async unprotectedArchiveToggle(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
createdBy: string,
|
createdBy: string,
|
||||||
projectId?: string,
|
projectId?: string,
|
||||||
@ -1476,6 +1496,7 @@ class FeatureToggleService {
|
|||||||
featureName,
|
featureName,
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
|
await this.validateNoOrphanParents([featureName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.validateNoChildren(featureName);
|
await this.validateNoChildren(featureName);
|
||||||
@ -1492,12 +1513,27 @@ class FeatureToggleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async archiveToggles(
|
async archiveToggles(
|
||||||
|
featureNames: string[],
|
||||||
|
user: User,
|
||||||
|
projectId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await this.stopWhenChangeRequestsEnabled(projectId, undefined, user);
|
||||||
|
await this.unprotectedArchiveToggles(
|
||||||
|
featureNames,
|
||||||
|
extractUsernameFromUser(user),
|
||||||
|
projectId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async unprotectedArchiveToggles(
|
||||||
featureNames: string[],
|
featureNames: string[],
|
||||||
createdBy: string,
|
createdBy: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.validateFeaturesContext(featureNames, projectId);
|
await Promise.all([
|
||||||
await this.validateNoOrphanParents(featureNames);
|
this.validateFeaturesContext(featureNames, projectId),
|
||||||
|
this.validateNoOrphanParents(featureNames),
|
||||||
|
]);
|
||||||
|
|
||||||
const features = await this.featureToggleStore.getAllByNames(
|
const features = await this.featureToggleStore.getAllByNames(
|
||||||
featureNames,
|
featureNames,
|
||||||
@ -2176,15 +2212,19 @@ class FeatureToggleService {
|
|||||||
|
|
||||||
private async stopWhenChangeRequestsEnabled(
|
private async stopWhenChangeRequestsEnabled(
|
||||||
project: string,
|
project: string,
|
||||||
environment: string,
|
environment?: string,
|
||||||
user?: User,
|
user?: User,
|
||||||
) {
|
) {
|
||||||
const canBypass =
|
const canBypass = environment
|
||||||
await this.changeRequestAccessReadModel.canBypassChangeRequest(
|
? await this.changeRequestAccessReadModel.canBypassChangeRequest(
|
||||||
project,
|
project,
|
||||||
environment,
|
environment,
|
||||||
user,
|
user,
|
||||||
);
|
)
|
||||||
|
: await this.changeRequestAccessReadModel.canBypassChangeRequestForProject(
|
||||||
|
project,
|
||||||
|
user,
|
||||||
|
);
|
||||||
if (!canBypass) {
|
if (!canBypass) {
|
||||||
throw new PermissionError(SKIP_CHANGE_REQUEST);
|
throw new PermissionError(SKIP_CHANGE_REQUEST);
|
||||||
}
|
}
|
||||||
|
@ -489,9 +489,8 @@ class FeatureController extends Controller {
|
|||||||
|
|
||||||
async archiveToggle(req: IAuthRequest, res: Response): Promise<void> {
|
async archiveToggle(req: IAuthRequest, res: Response): Promise<void> {
|
||||||
const { featureName } = req.params;
|
const { featureName } = req.params;
|
||||||
const userName = extractUsername(req);
|
|
||||||
|
|
||||||
await this.service.archiveToggle(featureName, userName);
|
await this.service.archiveToggle(featureName, req.user);
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,9 +140,8 @@ export default class ProjectArchiveController extends Controller {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { features } = req.body;
|
const { features } = req.body;
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
const userName = extractUsername(req);
|
|
||||||
|
|
||||||
await this.featureService.archiveToggles(features, userName, projectId);
|
await this.featureService.archiveToggles(features, req.user, projectId);
|
||||||
res.status(202).end();
|
res.status(202).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import {
|
|||||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||||
|
import User from '../../../../lib/types/user';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
const testUser = { name: 'test' } as User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_api_client', getLogger, {
|
db = await dbInit('feature_api_client', getLogger, {
|
||||||
@ -69,7 +71,7 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedX',
|
'featureArchivedX',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
@ -83,7 +85,7 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedY',
|
'featureArchivedY',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
'default',
|
'default',
|
||||||
@ -95,7 +97,7 @@ beforeAll(async () => {
|
|||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedZ',
|
'featureArchivedZ',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
'default',
|
'default',
|
||||||
|
@ -4,10 +4,12 @@ import {
|
|||||||
} from '../../helpers/test-helper';
|
} from '../../helpers/test-helper';
|
||||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import User from '../../../../lib/types/user';
|
||||||
// import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
// import { DEFAULT_ENV } from '../../../../lib/util/constants';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
const testUser = { name: 'test' } as User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_304_api_client', getLogger);
|
db = await dbInit('feature_304_api_client', getLogger);
|
||||||
@ -55,7 +57,7 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedX',
|
'featureArchivedX',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
|
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
@ -69,7 +71,7 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedY',
|
'featureArchivedY',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
'default',
|
'default',
|
||||||
@ -81,7 +83,7 @@ beforeAll(async () => {
|
|||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.archiveToggle(
|
await app.services.featureToggleServiceV2.archiveToggle(
|
||||||
'featureArchivedZ',
|
'featureArchivedZ',
|
||||||
'test',
|
testUser,
|
||||||
);
|
);
|
||||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||||
'default',
|
'default',
|
||||||
|
@ -1241,7 +1241,7 @@ test('should only count active feature toggles for project', async () => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await featureToggleService.archiveToggle('only-active-t2', 'me');
|
await featureToggleService.archiveToggle('only-active-t2', user);
|
||||||
|
|
||||||
const projects = await projectService.getProjects();
|
const projects = await projectService.getProjects();
|
||||||
const theProject = projects.find((p) => p.id === project.id);
|
const theProject = projects.find((p) => p.id === project.id);
|
||||||
@ -1265,7 +1265,7 @@ test('should list projects with all features archived', async () => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await featureToggleService.archiveToggle('archived-toggle', 'me');
|
await featureToggleService.archiveToggle('archived-toggle', user);
|
||||||
|
|
||||||
const projects = await projectService.getProjects();
|
const projects = await projectService.getProjects();
|
||||||
const theProject = projects.find((p) => p.id === project.id);
|
const theProject = projects.find((p) => p.id === project.id);
|
||||||
|
Loading…
Reference in New Issue
Block a user