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:
parent
668cb81384
commit
80049c2fa9
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -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 = {
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user