1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

feat: feature environments variants usage ui flag

This commit is contained in:
kwasniew 2024-05-14 14:28:10 +02:00
parent 668cb81384
commit 80049c2fa9
No known key found for this signature in database
GPG Key ID: 43A7CBC24C119560
8 changed files with 94 additions and 1 deletions

View File

@ -490,4 +490,12 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore {
); );
} }
} }
async variantExists(): Promise<boolean> {
const result = await this.db.raw(
`SELECT EXISTS (SELECT 1 FROM ${T.featureEnvs} WHERE variants <> '[]'::jsonb) AS present`,
);
const { present } = result.rows[0];
return present;
}
} }

View File

@ -8,6 +8,7 @@ import FakeClientMetricsStoreV2 from '../client-metrics/fake-client-metrics-stor
import FakeStrategiesStore from '../../../../test/fixtures/fake-strategies-store'; import FakeStrategiesStore from '../../../../test/fixtures/fake-strategies-store';
import FakeFeatureToggleStore from '../../feature-toggle/fakes/fake-feature-toggle-store'; import FakeFeatureToggleStore from '../../feature-toggle/fakes/fake-feature-toggle-store';
import type { IApplicationOverview } from './models'; import type { IApplicationOverview } from './models';
import FakeFeatureEnvironmentStore from '../../../../test/fixtures/fake-feature-environment-store';
let config: IUnleashConfig; let config: IUnleashConfig;
beforeAll(() => { beforeAll(() => {
@ -29,6 +30,7 @@ test('Multiple registrations of same appname and instanceid within same time per
featureToggleStore: new FakeFeatureToggleStore(), featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore, clientApplicationsStore,
clientInstanceStore, clientInstanceStore,
featureEnvironmentStore: new FakeFeatureEnvironmentStore(),
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
@ -79,6 +81,7 @@ test('Multiple unique clients causes multiple registrations', async () => {
featureToggleStore: new FakeFeatureToggleStore(), featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore, clientApplicationsStore,
clientInstanceStore, clientInstanceStore,
featureEnvironmentStore: new FakeFeatureEnvironmentStore(),
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
@ -129,6 +132,7 @@ test('Same client registered outside of dedup interval will be registered twice'
featureToggleStore: new FakeFeatureToggleStore(), featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore, clientApplicationsStore,
clientInstanceStore, clientInstanceStore,
featureEnvironmentStore: new FakeFeatureEnvironmentStore(),
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
@ -179,6 +183,7 @@ test('No registrations during a time period will not call stores', async () => {
featureToggleStore: new FakeFeatureToggleStore(), featureToggleStore: new FakeFeatureToggleStore(),
clientApplicationsStore, clientApplicationsStore,
clientInstanceStore, clientInstanceStore,
featureEnvironmentStore: new FakeFeatureEnvironmentStore(),
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,

View File

@ -1,6 +1,9 @@
import { APPLICATION_CREATED, CLIENT_REGISTER } from '../../../types/events'; import { APPLICATION_CREATED, CLIENT_REGISTER } from '../../../types/events';
import type { IApplication, IApplicationOverview } from './models'; import type { IApplication, IApplicationOverview } from './models';
import type { IUnleashStores } from '../../../types/stores'; import type {
IFeatureEnvironmentStore,
IUnleashStores,
} from '../../../types/stores';
import type { IUnleashConfig } from '../../../types/option'; import type { IUnleashConfig } from '../../../types/option';
import type { IEventStore } from '../../../types/stores/event-store'; import type { IEventStore } from '../../../types/stores/event-store';
import type { import type {
@ -46,6 +49,8 @@ export default class ClientInstanceService {
private privateProjectChecker: IPrivateProjectChecker; private privateProjectChecker: IPrivateProjectChecker;
private featureEnvironmentStore: IFeatureEnvironmentStore;
private flagResolver: IFlagResolver; private flagResolver: IFlagResolver;
constructor( constructor(
@ -55,6 +60,7 @@ export default class ClientInstanceService {
featureToggleStore, featureToggleStore,
clientInstanceStore, clientInstanceStore,
clientApplicationsStore, clientApplicationsStore,
featureEnvironmentStore,
eventStore, eventStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
@ -63,6 +69,7 @@ export default class ClientInstanceService {
| 'featureToggleStore' | 'featureToggleStore'
| 'clientApplicationsStore' | 'clientApplicationsStore'
| 'clientInstanceStore' | 'clientInstanceStore'
| 'featureEnvironmentStore'
| 'eventStore' | 'eventStore'
>, >,
{ {
@ -78,6 +85,7 @@ export default class ClientInstanceService {
this.clientInstanceStore = clientInstanceStore; this.clientInstanceStore = clientInstanceStore;
this.eventStore = eventStore; this.eventStore = eventStore;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.featureEnvironmentStore = featureEnvironmentStore;
this.flagResolver = flagResolver; this.flagResolver = flagResolver;
this.logger = getLogger( this.logger = getLogger(
'/services/client-metrics/client-instance-service.ts', '/services/client-metrics/client-instance-service.ts',
@ -294,4 +302,8 @@ export default class ClientInstanceService {
} }
}); });
} }
async usesFeatureEnvironmentVariants() {
return this.featureEnvironmentStore.variantExists();
}
} }

View File

@ -98,3 +98,39 @@ describe('displayUpgradeEdgeBanner', () => {
expect(body.flags.displayUpgradeEdgeBanner).toEqual(false); expect(body.flags.displayUpgradeEdgeBanner).toEqual(false);
}); });
}); });
describe('displayFeatureEnvironmentVariants', () => {
test('ui config should have displayFeatureEnvironmentVariants flag disabled if no env variants are used', async () => {
const { body } = await request
.get(`${base}/api/admin/ui-config`)
.expect('Content-Type', /json/)
.expect(200);
expect(body.flags).toBeTruthy();
expect(body.flags.displayFeatureEnvironmentVariants).toEqual(false);
});
test('ui config should have displayFeatureEnvironmentVariants flag enabled if env variants are used', async () => {
await stores.featureEnvironmentStore.addEnvironmentToFeature(
'test',
'default',
true,
);
await stores.featureEnvironmentStore.addVariantsToFeatureEnvironment(
'test',
'default',
[
{
name: 'a',
weight: 1,
weightType: 'fix',
stickiness: 'default',
},
],
);
const { body } = await request
.get(`${base}/api/admin/ui-config`)
.expect('Content-Type', /json/)
.expect(200);
expect(body.flags).toBeTruthy();
expect(body.flags.displayFeatureEnvironmentVariants).toEqual(true);
});
});

View File

@ -44,6 +44,8 @@ class ConfigController extends Controller {
private usesOldEdgeFunction: () => Promise<boolean>; private usesOldEdgeFunction: () => Promise<boolean>;
private usesFeatureEnvironmentVariants: () => Promise<boolean>;
private readonly openApiService: OpenApiService; private readonly openApiService: OpenApiService;
constructor( constructor(
@ -86,6 +88,14 @@ class ConfigController extends Controller {
maxAge: minutesToMilliseconds(10), maxAge: minutesToMilliseconds(10),
}, },
); );
this.usesFeatureEnvironmentVariants = memoizee(
async () =>
this.clientInstanceService.usesFeatureEnvironmentVariants(),
{
promise: true,
maxAge: minutesToMilliseconds(10),
},
);
this.route({ this.route({
method: 'get', method: 'get',
@ -134,11 +144,13 @@ class ConfigController extends Controller {
simpleAuthSettings, simpleAuthSettings,
maintenanceMode, maintenanceMode,
usesOldEdge, usesOldEdge,
usesFeatureEnvironmentVariants,
] = await Promise.all([ ] = await Promise.all([
this.frontendApiService.getFrontendSettings(false), this.frontendApiService.getFrontendSettings(false),
this.settingService.get<SimpleAuthSettings>(simpleAuthSettingsKey), this.settingService.get<SimpleAuthSettings>(simpleAuthSettingsKey),
this.maintenanceService.isMaintenanceMode(), this.maintenanceService.isMaintenanceMode(),
this.usesOldEdgeFunction(), this.usesOldEdgeFunction(),
this.usesFeatureEnvironmentVariants(),
]); ]);
const disablePasswordAuth = const disablePasswordAuth =
@ -155,6 +167,7 @@ class ConfigController extends Controller {
displayUpgradeEdgeBanner: displayUpgradeEdgeBanner:
usesOldEdge || usesOldEdge ||
this.config.flagResolver.isEnabled('displayEdgeBanner'), this.config.flagResolver.isEnabled('displayEdgeBanner'),
displayFeatureEnvironmentVariants: usesFeatureEnvironmentVariants,
}; };
const response: UiConfigSchema = { const response: UiConfigSchema = {

View File

@ -86,4 +86,6 @@ export interface IFeatureEnvironmentStore
): Promise<void>; ): Promise<void>;
clonePreviousVariants(environment: string, project: string): Promise<void>; clonePreviousVariants(environment: string, project: string): Promise<void>;
variantExists(): Promise<boolean>;
} }

View File

@ -98,3 +98,12 @@ test('sets ui config with frontendSettings', async () => {
expect(res.body.frontendApiOrigins).toEqual(frontendApiOrigins), expect(res.body.frontendApiOrigins).toEqual(frontendApiOrigins),
); );
}); });
test('ui config should have displayFeatureEnvironmentVariants flag disabled if no env variants are used', async () => {
const { body } = await app.request
.get('/api/admin/ui-config')
.expect('Content-Type', /json/)
.expect(200);
expect(body.flags).toBeTruthy();
expect(body.flags.displayFeatureEnvironmentVariants).toEqual(false);
});

View File

@ -245,4 +245,12 @@ export default class FakeFeatureEnvironmentStore
features.includes(featureEnv.featureName), features.includes(featureEnv.featureName),
); );
} }
async variantExists() {
return this.featureEnvironments.some(
(featureEnvironment) =>
featureEnvironment.variants &&
featureEnvironment.variants.length > 0,
);
}
} }