1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-19 17:52:45 +02:00

chore: remove dependent feature flags (#5419)

This commit is contained in:
Mateusz Kwasniewski 2023-11-27 14:54:40 +01:00 committed by GitHub
parent 581b238378
commit de287a75fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 83 additions and 193 deletions

View File

@ -31,9 +31,6 @@ const setupArchiveValidation = (orphanParents: string[]) => {
versionInfo: { versionInfo: {
current: { oss: 'version', enterprise: 'version' }, current: { oss: 'version', enterprise: 'version' },
}, },
flags: {
dependentFeatures: true,
},
}); });
testServerRoute( testServerRoute(
server, server,
@ -126,6 +123,7 @@ test('Skip change request', async () => {
await screen.findByText('Archive feature toggles'); await screen.findByText('Archive feature toggles');
const button = await screen.findByText('Archive toggles'); const button = await screen.findByText('Archive toggles');
await waitFor(() => expect(button).toBeEnabled());
button.click(); button.click();
await waitFor(() => { await waitFor(() => {

View File

@ -302,12 +302,8 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
const { disableArchive, offendingParents, hasDeletedDependencies } = const { disableArchive, offendingParents, hasDeletedDependencies } =
useVerifyArchive(featureIds, projectId, isOpen); useVerifyArchive(featureIds, projectId, isOpen);
const dependentFeatures = useUiFlag('dependentFeatures');
const removeDependenciesWarning = const removeDependenciesWarning =
dependentFeatures && offendingParents.length === 0 && hasDeletedDependencies;
offendingParents.length === 0 &&
hasDeletedDependencies;
return ( return (
<Dialogue <Dialogue
@ -317,7 +313,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
primaryButtonText={buttonText} primaryButtonText={buttonText}
secondaryButtonText='Cancel' secondaryButtonText='Cancel'
title={dialogTitle} title={dialogTitle}
disabledPrimaryButton={dependentFeatures && disableArchive} disabledPrimaryButton={disableArchive}
> >
<ConditionallyRender <ConditionallyRender
condition={isBulkArchive} condition={isBulkArchive}
@ -342,9 +338,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={ condition={offendingParents.length > 0}
dependentFeatures && offendingParents.length > 0
}
show={ show={
<ArchiveParentError <ArchiveParentError
ids={offendingParents} ids={offendingParents}
@ -378,9 +372,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
? ?
</p> </p>
<ConditionallyRender <ConditionallyRender
condition={ condition={offendingParents.length > 0}
dependentFeatures && offendingParents.length > 0
}
show={ show={
<ArchiveParentError <ArchiveParentError
ids={offendingParents} ids={offendingParents}

View File

@ -8,9 +8,6 @@ const server = testServerSetup();
const setupApi = () => { const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', { testServerRoute(server, '/api/admin/ui-config', {
flags: {
dependentFeatures: true,
},
versionInfo: { versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' }, current: { oss: 'irrelevant', enterprise: 'some value' },
}, },

View File

@ -10,9 +10,6 @@ const server = testServerSetup();
const setupApi = () => { const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', { testServerRoute(server, '/api/admin/ui-config', {
flags: {
dependentFeatures: true,
},
versionInfo: { versionInfo: {
current: { oss: 'irrelevant', enterprise: 'some value' }, current: { oss: 'irrelevant', enterprise: 'some value' },
}, },
@ -35,9 +32,6 @@ const setupApi = () => {
const setupOssWithExistingDependencies = () => { const setupOssWithExistingDependencies = () => {
testServerRoute(server, '/api/admin/ui-config', { testServerRoute(server, '/api/admin/ui-config', {
flags: {
dependentFeatures: true,
},
versionInfo: { versionInfo: {
current: { oss: 'some value' }, current: { oss: 'some value' },
}, },

View File

@ -1,13 +1,9 @@
import { useUiFlag } from 'hooks/useUiFlag';
import { useCheckDependenciesExist } from 'hooks/api/getters/useCheckDependenciesExist/useCheckDependenciesExist'; import { useCheckDependenciesExist } from 'hooks/api/getters/useCheckDependenciesExist/useCheckDependenciesExist';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const useShowDependentFeatures = (project: string) => { export const useShowDependentFeatures = (project: string) => {
const dependentFeatures = useUiFlag('dependentFeatures');
const { dependenciesExist } = useCheckDependenciesExist(project); const { dependenciesExist } = useCheckDependenciesExist(project);
const { isOss } = useUiConfig(); const { isOss } = useUiConfig();
return Boolean( return Boolean(isOss() ? dependenciesExist : true);
isOss() ? dependenciesExist && dependentFeatures : dependentFeatures,
);
}; };

View File

@ -10,11 +10,7 @@ import userEvent from '@testing-library/user-event';
const server = testServerSetup(); const server = testServerSetup();
const setupApi = () => { const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', { testServerRoute(server, '/api/admin/ui-config', {});
flags: {
dependentFeatures: true,
},
});
}; };
test('Cannot change project for feature with dependencies', async () => { test('Cannot change project for feature with dependencies', async () => {

View File

@ -41,7 +41,6 @@ const FeatureSettingsProjectConfirm = ({
feature, feature,
changeRequests, changeRequests,
}: IFeatureSettingsProjectConfirm) => { }: IFeatureSettingsProjectConfirm) => {
const dependentFeatures = useUiFlag('dependentFeatures');
const currentProjectId = useRequiredPathParam('projectId'); const currentProjectId = useRequiredPathParam('projectId');
const { project } = useProject(projectId); const { project } = useProject(projectId);
@ -61,8 +60,7 @@ const FeatureSettingsProjectConfirm = ({
: false; : false;
const hasDependencies = const hasDependencies =
dependentFeatures && feature.dependencies.length > 0 || feature.children.length > 0;
(feature.dependencies.length > 0 || feature.children.length > 0);
return ( return (
<ConditionallyRender <ConditionallyRender

View File

@ -143,7 +143,6 @@ export const FeatureView = () => {
const { refetch: projectRefetch } = useProject(projectId); const { refetch: projectRefetch } = useProject(projectId);
const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { favorite, unfavorite } = useFavoriteFeaturesApi();
const { refetchFeature } = useFeature(projectId, featureId); const { refetchFeature } = useFeature(projectId, featureId);
const dependentFeatures = useUiFlag('dependentFeatures');
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
const [openTagDialog, setOpenTagDialog] = useState(false); const [openTagDialog, setOpenTagDialog] = useState(false);
@ -267,10 +266,7 @@ export const FeatureView = () => {
/> />
</StyledToggleInfoContainer> </StyledToggleInfoContainer>
<ConditionallyRender <ConditionallyRender
condition={ condition={feature.dependencies.length > 0}
dependentFeatures &&
feature.dependencies.length > 0
}
show={ show={
<StyledDependency> <StyledDependency>
<b>Has parent: </b> <b>Has parent: </b>
@ -283,10 +279,7 @@ export const FeatureView = () => {
} }
/> />
<ConditionallyRender <ConditionallyRender
condition={ condition={feature.children.length > 0}
dependentFeatures &&
feature.children.length > 0
}
show={ show={
<StyledDependency> <StyledDependency>
<b>Has children:</b> <b>Has children:</b>

View File

@ -75,7 +75,6 @@ exports[`should create default config 1`] = `
"caseInsensitiveInOperators": false, "caseInsensitiveInOperators": false,
"customRootRolesKillSwitch": false, "customRootRolesKillSwitch": false,
"demo": false, "demo": false,
"dependentFeatures": false,
"detectSegmentUsageInChangeRequests": false, "detectSegmentUsageInChangeRequests": false,
"disableBulkToggle": false, "disableBulkToggle": false,
"disableMetrics": false, "disableMetrics": false,

View File

@ -67,8 +67,6 @@ export default class FeatureToggleClientStore
const isPlayground = requestType === 'playground'; const isPlayground = requestType === 'playground';
const environment = featureQuery?.environment || DEFAULT_ENV; const environment = featureQuery?.environment || DEFAULT_ENV;
const stopTimer = this.timer('getFeatureAdmin'); const stopTimer = this.timer('getFeatureAdmin');
const dependentFeaturesEnabled =
this.flagResolver.isEnabled('dependentFeatures');
let selectColumns = [ let selectColumns = [
'features.name as name', 'features.name as name',
@ -201,7 +199,7 @@ export default class FeatureToggleClientStore
) { ) {
this.addSegmentIdsToStrategy(feature, r); this.addSegmentIdsToStrategy(feature, r);
} }
if (r.parent && !isAdmin && dependentFeaturesEnabled) { if (r.parent && !isAdmin) {
feature.dependencies = feature.dependencies || []; feature.dependencies = feature.dependencies || [];
feature.dependencies.push({ feature.dependencies.push({
feature: r.parent, feature: r.parent,

View File

@ -64,7 +64,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -182,25 +182,19 @@ export default class DependentFeaturesController extends Controller {
const { child, projectId } = req.params; const { child, projectId } = req.params;
const { variants, enabled, feature } = req.body; const { variants, enabled, feature } = req.body;
if (this.config.flagResolver.isEnabled('dependentFeatures')) { await this.dependentFeaturesService.transactional((service) =>
await this.dependentFeaturesService.transactional((service) => service.upsertFeatureDependency(
service.upsertFeatureDependency( { child, projectId },
{ child, projectId }, {
{ variants,
variants, enabled,
enabled, feature,
feature, },
}, req.user,
req.user, ),
), );
);
res.status(200).end(); res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
} }
async deleteFeatureDependency( async deleteFeatureDependency(
@ -209,23 +203,17 @@ export default class DependentFeaturesController extends Controller {
): Promise<void> { ): Promise<void> {
const { child, parent, projectId } = req.params; const { child, parent, projectId } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) { await this.dependentFeaturesService.transactional((service) =>
await this.dependentFeaturesService.transactional((service) => service.deleteFeatureDependency(
service.deleteFeatureDependency( {
{ parent,
parent, child,
child, },
}, projectId,
projectId, req.user,
req.user, ),
), );
); res.status(200).end();
res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
} }
async deleteFeatureDependencies( async deleteFeatureDependencies(
@ -234,20 +222,10 @@ export default class DependentFeaturesController extends Controller {
): Promise<void> { ): Promise<void> {
const { child, projectId } = req.params; const { child, projectId } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) { await this.dependentFeaturesService.transactional((service) =>
await this.dependentFeaturesService.transactional((service) => service.deleteFeaturesDependencies([child], projectId, req.user),
service.deleteFeaturesDependencies( );
[child], res.status(200).end();
projectId,
req.user,
),
);
res.status(200).end();
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
} }
async getParentOptions( async getParentOptions(
@ -256,15 +234,9 @@ export default class DependentFeaturesController extends Controller {
): Promise<void> { ): Promise<void> {
const { child } = req.params; const { child } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) { const parentOptions =
const parentOptions = await this.dependentFeaturesService.getParentOptions(child);
await this.dependentFeaturesService.getParentOptions(child); res.send(parentOptions);
res.send(parentOptions);
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
} }
async checkDependenciesExist( async checkDependenciesExist(
@ -273,14 +245,8 @@ export default class DependentFeaturesController extends Controller {
): Promise<void> { ): Promise<void> {
const { child } = req.params; const { child } = req.params;
if (this.config.flagResolver.isEnabled('dependentFeatures')) { const exist =
const exist = await this.dependentFeaturesService.checkDependenciesExist();
await this.dependentFeaturesService.checkDependenciesExist(); res.send(exist);
res.send(exist);
} else {
throw new InvalidOperationError(
'Dependent features are not enabled',
);
}
} }
} }

View File

@ -25,7 +25,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -159,7 +159,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
featuresExportImport: true, featuresExportImport: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -202,7 +202,6 @@ export class FeatureToggleRowConverter {
buildPlaygroundFeaturesFromRows = ( buildPlaygroundFeaturesFromRows = (
rows: any[], rows: any[],
dependentFeaturesEnabled: boolean,
featureQuery?: IFeatureToggleQuery, featureQuery?: IFeatureToggleQuery,
): FeatureConfigurationClient[] => { ): FeatureConfigurationClient[] => {
const result = rows.reduce((acc, r) => { const result = rows.reduce((acc, r) => {
@ -212,7 +211,7 @@ export class FeatureToggleRowConverter {
feature = this.createBaseFeature(r, feature, featureQuery); feature = this.createBaseFeature(r, feature, featureQuery);
if (r.parent && dependentFeaturesEnabled) { if (r.parent) {
feature.dependencies = feature.dependencies || []; feature.dependencies = feature.dependencies || [];
feature.dependencies.push({ feature.dependencies.push({
feature: r.parent, feature: r.parent,

View File

@ -130,6 +130,7 @@ export type FeatureNameCheckResultWithFeaturePattern =
const oneOf = (values: string[], match: string) => { const oneOf = (values: string[], match: string) => {
return values.some((value) => value === match); return values.some((value) => value === match);
}; };
class FeatureToggleService { class FeatureToggleService {
private logger: Logger; private logger: Logger;
@ -256,32 +257,27 @@ class FeatureToggleService {
} }
async validateNoChildren(featureName: string): Promise<void> { async validateNoChildren(featureName: string): Promise<void> {
if (this.flagResolver.isEnabled('dependentFeatures')) { const children = await this.dependentFeaturesReadModel.getChildren([
const children = await this.dependentFeaturesReadModel.getChildren([ featureName,
featureName, ]);
]); if (children.length > 0) {
if (children.length > 0) { throw new InvalidOperationError(
throw new InvalidOperationError( 'You can not archive/delete this feature since other features depend on it.',
'You can not archive/delete this feature since other features depend on it.', );
);
}
} }
} }
async validateNoOrphanParents(featureNames: string[]): Promise<void> { async validateNoOrphanParents(featureNames: string[]): Promise<void> {
if (this.flagResolver.isEnabled('dependentFeatures')) { if (featureNames.length === 0) return;
if (featureNames.length === 0) return; const parents = await this.dependentFeaturesReadModel.getOrphanParents(
const parents = featureNames,
await this.dependentFeaturesReadModel.getOrphanParents( );
featureNames, if (parents.length > 0) {
); throw new InvalidOperationError(
if (parents.length > 0) { featureNames.length > 1
throw new InvalidOperationError( ? `You can not archive/delete those features since other features depend on them.`
featureNames.length > 1 : `You can not archive/delete this feature since other features depend on it.`,
? `You can not archive/delete those features since other features depend on them.` );
: `You can not archive/delete this feature since other features depend on it.`,
);
}
} }
} }
@ -959,12 +955,10 @@ class FeatureToggleService {
let dependencies: IDependency[] = []; let dependencies: IDependency[] = [];
let children: string[] = []; let children: string[] = [];
if (this.flagResolver.isEnabled('dependentFeatures')) { [dependencies, children] = await Promise.all([
[dependencies, children] = await Promise.all([ this.dependentFeaturesReadModel.getParents(featureName),
this.dependentFeaturesReadModel.getParents(featureName), this.dependentFeaturesReadModel.getChildren([featureName]),
this.dependentFeaturesReadModel.getChildren([featureName]), ]);
]);
}
if (environmentVariants) { if (environmentVariants) {
const result = const result =
@ -1288,21 +1282,17 @@ class FeatureToggleService {
}), }),
); );
if (this.flagResolver.isEnabled('dependentFeatures')) { const cloneDependencies =
const cloneDependencies = this.dependentFeaturesService.cloneDependencies(
this.dependentFeaturesService.cloneDependencies( { featureName, newFeatureName, projectId },
{ featureName, newFeatureName, projectId }, userName,
userName, );
);
await Promise.all([ await Promise.all([
...strategyTasks, ...strategyTasks,
...variantTasks, ...variantTasks,
cloneDependencies, cloneDependencies,
]); ]);
} else {
await Promise.all([...strategyTasks, ...variantTasks]);
}
return created; return created;
} }

View File

@ -209,16 +209,11 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
const archived = false; const archived = false;
const builder = this.getBaseFeatureQuery(archived, environment); const builder = this.getBaseFeatureQuery(archived, environment);
const dependentFeaturesEnabled = builder.withDependentFeatureToggles();
this.flagResolver.isEnabled('dependentFeatures');
if (dependentFeaturesEnabled) { builder.addSelectColumn('df.parent as parent');
builder.withDependentFeatureToggles(); builder.addSelectColumn('df.variants as parent_variants');
builder.addSelectColumn('df.enabled as parent_enabled');
builder.addSelectColumn('df.parent as parent');
builder.addSelectColumn('df.variants as parent_variants');
builder.addSelectColumn('df.enabled as parent_enabled');
}
if (featureQuery?.project) { if (featureQuery?.project) {
builder.forProject(featureQuery.project); builder.forProject(featureQuery.project);
@ -230,7 +225,6 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows( return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
rows, rows,
dependentFeaturesEnabled,
featureQuery, featureQuery,
); );
} }

View File

@ -22,7 +22,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
useLastSeenRefactor: true, useLastSeenRefactor: true,
}, },
}, },

View File

@ -93,7 +93,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -10,11 +10,7 @@ let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('advanced_playground', getLogger, { db = await dbInit('advanced_playground', getLogger);
experimental: {
flags: { dependentFeatures: true },
},
});
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
db.stores, db.stores,
{ {
@ -24,7 +20,6 @@ beforeAll(async () => {
strictSchemaValidation: true, strictSchemaValidation: true,
strategyVariant: true, strategyVariant: true,
privateProjects: true, privateProjects: true,
dependentFeatures: true,
useLastSeenRefactor: true, useLastSeenRefactor: true,
}, },
}, },

View File

@ -24,7 +24,6 @@ export type IFlagKey =
| 'filterInvalidClientMetrics' | 'filterInvalidClientMetrics'
| 'customRootRolesKillSwitch' | 'customRootRolesKillSwitch'
| 'privateProjects' | 'privateProjects'
| 'dependentFeatures'
| 'disableMetrics' | 'disableMetrics'
| 'useLastSeenRefactor' | 'useLastSeenRefactor'
| 'banners' | 'banners'
@ -107,10 +106,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH, process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH,
false, false,
), ),
dependentFeatures: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_DEPENDENT_FEATURES,
false,
),
privateProjects: parseEnvVarBoolean( privateProjects: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS, process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS,
false, false,

View File

@ -38,7 +38,6 @@ process.nextTick(async () => {
anonymiseEventLog: false, anonymiseEventLog: false,
responseTimeWithAppNameKillSwitch: false, responseTimeWithAppNameKillSwitch: false,
privateProjects: true, privateProjects: true,
dependentFeatures: true,
useLastSeenRefactor: true, useLastSeenRefactor: true,
featureSearchAPI: true, featureSearchAPI: true,
featureSearchFrontend: false, featureSearchFrontend: false,

View File

@ -17,7 +17,6 @@ beforeAll(async () => {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },

View File

@ -12,16 +12,13 @@ let db: ITestDb;
const testUser = { name: 'test' } as User; const testUser = { name: 'test' } as User;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_api_client', getLogger, { db = await dbInit('feature_api_client', getLogger);
experimental: { flags: { dependentFeatures: true } },
});
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
db.stores, db.stores,
{ {
experimental: { experimental: {
flags: { flags: {
strictSchemaValidation: true, strictSchemaValidation: true,
dependentFeatures: true,
}, },
}, },
}, },