mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
chore: remove dependent feature flags (#5419)
This commit is contained in:
parent
581b238378
commit
de287a75fe
@ -31,9 +31,6 @@ const setupArchiveValidation = (orphanParents: string[]) => {
|
||||
versionInfo: {
|
||||
current: { oss: 'version', enterprise: 'version' },
|
||||
},
|
||||
flags: {
|
||||
dependentFeatures: true,
|
||||
},
|
||||
});
|
||||
testServerRoute(
|
||||
server,
|
||||
@ -126,6 +123,7 @@ test('Skip change request', async () => {
|
||||
await screen.findByText('Archive feature toggles');
|
||||
const button = await screen.findByText('Archive toggles');
|
||||
|
||||
await waitFor(() => expect(button).toBeEnabled());
|
||||
button.click();
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -302,12 +302,8 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
|
||||
const { disableArchive, offendingParents, hasDeletedDependencies } =
|
||||
useVerifyArchive(featureIds, projectId, isOpen);
|
||||
|
||||
const dependentFeatures = useUiFlag('dependentFeatures');
|
||||
|
||||
const removeDependenciesWarning =
|
||||
dependentFeatures &&
|
||||
offendingParents.length === 0 &&
|
||||
hasDeletedDependencies;
|
||||
offendingParents.length === 0 && hasDeletedDependencies;
|
||||
|
||||
return (
|
||||
<Dialogue
|
||||
@ -317,7 +313,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
|
||||
primaryButtonText={buttonText}
|
||||
secondaryButtonText='Cancel'
|
||||
title={dialogTitle}
|
||||
disabledPrimaryButton={dependentFeatures && disableArchive}
|
||||
disabledPrimaryButton={disableArchive}
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={isBulkArchive}
|
||||
@ -342,9 +338,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
dependentFeatures && offendingParents.length > 0
|
||||
}
|
||||
condition={offendingParents.length > 0}
|
||||
show={
|
||||
<ArchiveParentError
|
||||
ids={offendingParents}
|
||||
@ -378,9 +372,7 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
|
||||
?
|
||||
</p>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
dependentFeatures && offendingParents.length > 0
|
||||
}
|
||||
condition={offendingParents.length > 0}
|
||||
show={
|
||||
<ArchiveParentError
|
||||
ids={offendingParents}
|
||||
|
@ -8,9 +8,6 @@ const server = testServerSetup();
|
||||
|
||||
const setupApi = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
dependentFeatures: true,
|
||||
},
|
||||
versionInfo: {
|
||||
current: { oss: 'irrelevant', enterprise: 'some value' },
|
||||
},
|
||||
|
@ -10,9 +10,6 @@ const server = testServerSetup();
|
||||
|
||||
const setupApi = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
dependentFeatures: true,
|
||||
},
|
||||
versionInfo: {
|
||||
current: { oss: 'irrelevant', enterprise: 'some value' },
|
||||
},
|
||||
@ -35,9 +32,6 @@ const setupApi = () => {
|
||||
|
||||
const setupOssWithExistingDependencies = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
dependentFeatures: true,
|
||||
},
|
||||
versionInfo: {
|
||||
current: { oss: 'some value' },
|
||||
},
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { useCheckDependenciesExist } from 'hooks/api/getters/useCheckDependenciesExist/useCheckDependenciesExist';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
export const useShowDependentFeatures = (project: string) => {
|
||||
const dependentFeatures = useUiFlag('dependentFeatures');
|
||||
const { dependenciesExist } = useCheckDependenciesExist(project);
|
||||
const { isOss } = useUiConfig();
|
||||
|
||||
return Boolean(
|
||||
isOss() ? dependenciesExist && dependentFeatures : dependentFeatures,
|
||||
);
|
||||
return Boolean(isOss() ? dependenciesExist : true);
|
||||
};
|
||||
|
@ -10,11 +10,7 @@ import userEvent from '@testing-library/user-event';
|
||||
const server = testServerSetup();
|
||||
|
||||
const setupApi = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
flags: {
|
||||
dependentFeatures: true,
|
||||
},
|
||||
});
|
||||
testServerRoute(server, '/api/admin/ui-config', {});
|
||||
};
|
||||
|
||||
test('Cannot change project for feature with dependencies', async () => {
|
||||
|
@ -41,7 +41,6 @@ const FeatureSettingsProjectConfirm = ({
|
||||
feature,
|
||||
changeRequests,
|
||||
}: IFeatureSettingsProjectConfirm) => {
|
||||
const dependentFeatures = useUiFlag('dependentFeatures');
|
||||
const currentProjectId = useRequiredPathParam('projectId');
|
||||
const { project } = useProject(projectId);
|
||||
|
||||
@ -61,8 +60,7 @@ const FeatureSettingsProjectConfirm = ({
|
||||
: false;
|
||||
|
||||
const hasDependencies =
|
||||
dependentFeatures &&
|
||||
(feature.dependencies.length > 0 || feature.children.length > 0);
|
||||
feature.dependencies.length > 0 || feature.children.length > 0;
|
||||
|
||||
return (
|
||||
<ConditionallyRender
|
||||
|
@ -143,7 +143,6 @@ export const FeatureView = () => {
|
||||
const { refetch: projectRefetch } = useProject(projectId);
|
||||
const { favorite, unfavorite } = useFavoriteFeaturesApi();
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const dependentFeatures = useUiFlag('dependentFeatures');
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const [openTagDialog, setOpenTagDialog] = useState(false);
|
||||
@ -267,10 +266,7 @@ export const FeatureView = () => {
|
||||
/>
|
||||
</StyledToggleInfoContainer>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
dependentFeatures &&
|
||||
feature.dependencies.length > 0
|
||||
}
|
||||
condition={feature.dependencies.length > 0}
|
||||
show={
|
||||
<StyledDependency>
|
||||
<b>Has parent: </b>
|
||||
@ -283,10 +279,7 @@ export const FeatureView = () => {
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
dependentFeatures &&
|
||||
feature.children.length > 0
|
||||
}
|
||||
condition={feature.children.length > 0}
|
||||
show={
|
||||
<StyledDependency>
|
||||
<b>Has children:</b>
|
||||
|
@ -75,7 +75,6 @@ exports[`should create default config 1`] = `
|
||||
"caseInsensitiveInOperators": false,
|
||||
"customRootRolesKillSwitch": false,
|
||||
"demo": false,
|
||||
"dependentFeatures": false,
|
||||
"detectSegmentUsageInChangeRequests": false,
|
||||
"disableBulkToggle": false,
|
||||
"disableMetrics": false,
|
||||
|
@ -67,8 +67,6 @@ export default class FeatureToggleClientStore
|
||||
const isPlayground = requestType === 'playground';
|
||||
const environment = featureQuery?.environment || DEFAULT_ENV;
|
||||
const stopTimer = this.timer('getFeatureAdmin');
|
||||
const dependentFeaturesEnabled =
|
||||
this.flagResolver.isEnabled('dependentFeatures');
|
||||
|
||||
let selectColumns = [
|
||||
'features.name as name',
|
||||
@ -201,7 +199,7 @@ export default class FeatureToggleClientStore
|
||||
) {
|
||||
this.addSegmentIdsToStrategy(feature, r);
|
||||
}
|
||||
if (r.parent && !isAdmin && dependentFeaturesEnabled) {
|
||||
if (r.parent && !isAdmin) {
|
||||
feature.dependencies = feature.dependencies || [];
|
||||
feature.dependencies.push({
|
||||
feature: r.parent,
|
||||
|
@ -64,7 +64,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -182,25 +182,19 @@ export default class DependentFeaturesController extends Controller {
|
||||
const { child, projectId } = req.params;
|
||||
const { variants, enabled, feature } = req.body;
|
||||
|
||||
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.upsertFeatureDependency(
|
||||
{ child, projectId },
|
||||
{
|
||||
variants,
|
||||
enabled,
|
||||
feature,
|
||||
},
|
||||
req.user,
|
||||
),
|
||||
);
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.upsertFeatureDependency(
|
||||
{ child, projectId },
|
||||
{
|
||||
variants,
|
||||
enabled,
|
||||
feature,
|
||||
},
|
||||
req.user,
|
||||
),
|
||||
);
|
||||
|
||||
res.status(200).end();
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Dependent features are not enabled',
|
||||
);
|
||||
}
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async deleteFeatureDependency(
|
||||
@ -209,23 +203,17 @@ export default class DependentFeaturesController extends Controller {
|
||||
): Promise<void> {
|
||||
const { child, parent, projectId } = req.params;
|
||||
|
||||
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.deleteFeatureDependency(
|
||||
{
|
||||
parent,
|
||||
child,
|
||||
},
|
||||
projectId,
|
||||
req.user,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Dependent features are not enabled',
|
||||
);
|
||||
}
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.deleteFeatureDependency(
|
||||
{
|
||||
parent,
|
||||
child,
|
||||
},
|
||||
projectId,
|
||||
req.user,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async deleteFeatureDependencies(
|
||||
@ -234,20 +222,10 @@ export default class DependentFeaturesController extends Controller {
|
||||
): Promise<void> {
|
||||
const { child, projectId } = req.params;
|
||||
|
||||
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.deleteFeaturesDependencies(
|
||||
[child],
|
||||
projectId,
|
||||
req.user,
|
||||
),
|
||||
);
|
||||
res.status(200).end();
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Dependent features are not enabled',
|
||||
);
|
||||
}
|
||||
await this.dependentFeaturesService.transactional((service) =>
|
||||
service.deleteFeaturesDependencies([child], projectId, req.user),
|
||||
);
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
async getParentOptions(
|
||||
@ -256,15 +234,9 @@ export default class DependentFeaturesController extends Controller {
|
||||
): Promise<void> {
|
||||
const { child } = req.params;
|
||||
|
||||
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
|
||||
const parentOptions =
|
||||
await this.dependentFeaturesService.getParentOptions(child);
|
||||
res.send(parentOptions);
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Dependent features are not enabled',
|
||||
);
|
||||
}
|
||||
const parentOptions =
|
||||
await this.dependentFeaturesService.getParentOptions(child);
|
||||
res.send(parentOptions);
|
||||
}
|
||||
|
||||
async checkDependenciesExist(
|
||||
@ -273,14 +245,8 @@ export default class DependentFeaturesController extends Controller {
|
||||
): Promise<void> {
|
||||
const { child } = req.params;
|
||||
|
||||
if (this.config.flagResolver.isEnabled('dependentFeatures')) {
|
||||
const exist =
|
||||
await this.dependentFeaturesService.checkDependenciesExist();
|
||||
res.send(exist);
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Dependent features are not enabled',
|
||||
);
|
||||
}
|
||||
const exist =
|
||||
await this.dependentFeaturesService.checkDependenciesExist();
|
||||
res.send(exist);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -159,7 +159,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
featuresExportImport: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -202,7 +202,6 @@ export class FeatureToggleRowConverter {
|
||||
|
||||
buildPlaygroundFeaturesFromRows = (
|
||||
rows: any[],
|
||||
dependentFeaturesEnabled: boolean,
|
||||
featureQuery?: IFeatureToggleQuery,
|
||||
): FeatureConfigurationClient[] => {
|
||||
const result = rows.reduce((acc, r) => {
|
||||
@ -212,7 +211,7 @@ export class FeatureToggleRowConverter {
|
||||
|
||||
feature = this.createBaseFeature(r, feature, featureQuery);
|
||||
|
||||
if (r.parent && dependentFeaturesEnabled) {
|
||||
if (r.parent) {
|
||||
feature.dependencies = feature.dependencies || [];
|
||||
feature.dependencies.push({
|
||||
feature: r.parent,
|
||||
|
@ -130,6 +130,7 @@ export type FeatureNameCheckResultWithFeaturePattern =
|
||||
const oneOf = (values: string[], match: string) => {
|
||||
return values.some((value) => value === match);
|
||||
};
|
||||
|
||||
class FeatureToggleService {
|
||||
private logger: Logger;
|
||||
|
||||
@ -256,32 +257,27 @@ class FeatureToggleService {
|
||||
}
|
||||
|
||||
async validateNoChildren(featureName: string): Promise<void> {
|
||||
if (this.flagResolver.isEnabled('dependentFeatures')) {
|
||||
const children = await this.dependentFeaturesReadModel.getChildren([
|
||||
featureName,
|
||||
]);
|
||||
if (children.length > 0) {
|
||||
throw new InvalidOperationError(
|
||||
'You can not archive/delete this feature since other features depend on it.',
|
||||
);
|
||||
}
|
||||
const children = await this.dependentFeaturesReadModel.getChildren([
|
||||
featureName,
|
||||
]);
|
||||
if (children.length > 0) {
|
||||
throw new InvalidOperationError(
|
||||
'You can not archive/delete this feature since other features depend on it.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async validateNoOrphanParents(featureNames: string[]): Promise<void> {
|
||||
if (this.flagResolver.isEnabled('dependentFeatures')) {
|
||||
if (featureNames.length === 0) return;
|
||||
const parents =
|
||||
await this.dependentFeaturesReadModel.getOrphanParents(
|
||||
featureNames,
|
||||
);
|
||||
if (parents.length > 0) {
|
||||
throw new InvalidOperationError(
|
||||
featureNames.length > 1
|
||||
? `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.`,
|
||||
);
|
||||
}
|
||||
if (featureNames.length === 0) return;
|
||||
const parents = await this.dependentFeaturesReadModel.getOrphanParents(
|
||||
featureNames,
|
||||
);
|
||||
if (parents.length > 0) {
|
||||
throw new InvalidOperationError(
|
||||
featureNames.length > 1
|
||||
? `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 children: string[] = [];
|
||||
if (this.flagResolver.isEnabled('dependentFeatures')) {
|
||||
[dependencies, children] = await Promise.all([
|
||||
this.dependentFeaturesReadModel.getParents(featureName),
|
||||
this.dependentFeaturesReadModel.getChildren([featureName]),
|
||||
]);
|
||||
}
|
||||
[dependencies, children] = await Promise.all([
|
||||
this.dependentFeaturesReadModel.getParents(featureName),
|
||||
this.dependentFeaturesReadModel.getChildren([featureName]),
|
||||
]);
|
||||
|
||||
if (environmentVariants) {
|
||||
const result =
|
||||
@ -1288,21 +1282,17 @@ class FeatureToggleService {
|
||||
}),
|
||||
);
|
||||
|
||||
if (this.flagResolver.isEnabled('dependentFeatures')) {
|
||||
const cloneDependencies =
|
||||
this.dependentFeaturesService.cloneDependencies(
|
||||
{ featureName, newFeatureName, projectId },
|
||||
userName,
|
||||
);
|
||||
const cloneDependencies =
|
||||
this.dependentFeaturesService.cloneDependencies(
|
||||
{ featureName, newFeatureName, projectId },
|
||||
userName,
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
...strategyTasks,
|
||||
...variantTasks,
|
||||
cloneDependencies,
|
||||
]);
|
||||
} else {
|
||||
await Promise.all([...strategyTasks, ...variantTasks]);
|
||||
}
|
||||
await Promise.all([
|
||||
...strategyTasks,
|
||||
...variantTasks,
|
||||
cloneDependencies,
|
||||
]);
|
||||
|
||||
return created;
|
||||
}
|
||||
|
@ -209,16 +209,11 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
const archived = false;
|
||||
const builder = this.getBaseFeatureQuery(archived, environment);
|
||||
|
||||
const dependentFeaturesEnabled =
|
||||
this.flagResolver.isEnabled('dependentFeatures');
|
||||
builder.withDependentFeatureToggles();
|
||||
|
||||
if (dependentFeaturesEnabled) {
|
||||
builder.withDependentFeatureToggles();
|
||||
|
||||
builder.addSelectColumn('df.parent as parent');
|
||||
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) {
|
||||
builder.forProject(featureQuery.project);
|
||||
@ -230,7 +225,6 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
||||
|
||||
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
|
||||
rows,
|
||||
dependentFeaturesEnabled,
|
||||
featureQuery,
|
||||
);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
useLastSeenRefactor: true,
|
||||
},
|
||||
},
|
||||
|
@ -93,7 +93,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -10,11 +10,7 @@ let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('advanced_playground', getLogger, {
|
||||
experimental: {
|
||||
flags: { dependentFeatures: true },
|
||||
},
|
||||
});
|
||||
db = await dbInit('advanced_playground', getLogger);
|
||||
app = await setupAppWithCustomConfig(
|
||||
db.stores,
|
||||
{
|
||||
@ -24,7 +20,6 @@ beforeAll(async () => {
|
||||
strictSchemaValidation: true,
|
||||
strategyVariant: true,
|
||||
privateProjects: true,
|
||||
dependentFeatures: true,
|
||||
useLastSeenRefactor: true,
|
||||
},
|
||||
},
|
||||
|
@ -24,7 +24,6 @@ export type IFlagKey =
|
||||
| 'filterInvalidClientMetrics'
|
||||
| 'customRootRolesKillSwitch'
|
||||
| 'privateProjects'
|
||||
| 'dependentFeatures'
|
||||
| 'disableMetrics'
|
||||
| 'useLastSeenRefactor'
|
||||
| 'banners'
|
||||
@ -107,10 +106,6 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES_KILL_SWITCH,
|
||||
false,
|
||||
),
|
||||
dependentFeatures: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_DEPENDENT_FEATURES,
|
||||
false,
|
||||
),
|
||||
privateProjects: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_PRIVATE_PROJECTS,
|
||||
false,
|
||||
|
@ -38,7 +38,6 @@ process.nextTick(async () => {
|
||||
anonymiseEventLog: false,
|
||||
responseTimeWithAppNameKillSwitch: false,
|
||||
privateProjects: true,
|
||||
dependentFeatures: true,
|
||||
useLastSeenRefactor: true,
|
||||
featureSearchAPI: true,
|
||||
featureSearchFrontend: false,
|
||||
|
@ -17,7 +17,6 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -12,16 +12,13 @@ let db: ITestDb;
|
||||
const testUser = { name: 'test' } as User;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_api_client', getLogger, {
|
||||
experimental: { flags: { dependentFeatures: true } },
|
||||
});
|
||||
db = await dbInit('feature_api_client', getLogger);
|
||||
app = await setupAppWithCustomConfig(
|
||||
db.stores,
|
||||
{
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user