mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
chore: remove new frontend api feature flag (#6906)
The flag has been 100% for a bit now, we need to prepare for GA.
This commit is contained in:
parent
bf3366434c
commit
d578deab7f
@ -121,7 +121,6 @@ exports[`should create default config 1`] = `
|
||||
},
|
||||
},
|
||||
"filterInvalidClientMetrics": false,
|
||||
"globalFrontendApiCache": false,
|
||||
"googleAuthEnabled": false,
|
||||
"inMemoryScheduledChangeRequests": false,
|
||||
"maintenanceMode": false,
|
||||
@ -144,7 +143,6 @@ exports[`should create default config 1`] = `
|
||||
"queryMissingTokens": false,
|
||||
"responseTimeMetricsFix": false,
|
||||
"responseTimeWithAppNameKillSwitch": false,
|
||||
"returnGlobalFrontendApiCache": false,
|
||||
"scimApi": false,
|
||||
"showInactiveUsers": false,
|
||||
"signals": false,
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
emptyResponse,
|
||||
getStandardResponses,
|
||||
type FrontendApiClientSchema,
|
||||
type FrontendApiFeatureSchema,
|
||||
frontendApiFeaturesSchema,
|
||||
type FrontendApiFeaturesSchema,
|
||||
} from '../../openapi';
|
||||
@ -21,8 +20,6 @@ import NotImplementedError from '../../error/not-implemented-error';
|
||||
import NotFoundError from '../../error/notfound-error';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { minutesToMilliseconds } from 'date-fns';
|
||||
import isEqual from 'lodash.isequal';
|
||||
import { diff } from 'json-diff';
|
||||
import metricsHelper from '../../util/metrics-helper';
|
||||
import { FUNCTION_TIME } from '../../metric-events';
|
||||
|
||||
@ -186,48 +183,11 @@ export default class FrontendAPIController extends Controller {
|
||||
if (!this.config.flagResolver.isEnabled('embedProxy')) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
let toggles: FrontendApiFeatureSchema[];
|
||||
let newToggles: FrontendApiFeatureSchema[] = [];
|
||||
if (this.config.flagResolver.isEnabled('globalFrontendApiCache')) {
|
||||
const context = FrontendAPIController.createContext(req);
|
||||
[toggles, newToggles] = await Promise.all([
|
||||
this.getTimedFrontendApiFeatures(req, context),
|
||||
this.getTimedNewFrontendApiFeatures(req, context),
|
||||
]);
|
||||
const sortedToggles = toggles.sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
);
|
||||
const sortedNewToggles = newToggles.sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
);
|
||||
if (!isEqual(sortedToggles, sortedNewToggles)) {
|
||||
this.logger.warn(
|
||||
`old features and new features are different. Old count ${
|
||||
toggles.length
|
||||
}, new count ${newToggles.length}, projects ${
|
||||
req.user.projects
|
||||
}, environment ${
|
||||
req.user.environment
|
||||
}, diff ${JSON.stringify(
|
||||
diff(sortedToggles, sortedNewToggles),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
this.config.flagResolver.isEnabled('returnGlobalFrontendApiCache')
|
||||
) {
|
||||
toggles =
|
||||
await this.services.frontendApiService.getNewFrontendApiFeatures(
|
||||
req.user,
|
||||
FrontendAPIController.createContext(req),
|
||||
);
|
||||
} else {
|
||||
toggles =
|
||||
const toggles =
|
||||
await this.services.frontendApiService.getFrontendApiFeatures(
|
||||
req.user,
|
||||
FrontendAPIController.createContext(req),
|
||||
);
|
||||
}
|
||||
|
||||
res.set('Cache-control', 'no-cache');
|
||||
|
||||
@ -239,28 +199,6 @@ export default class FrontendAPIController extends Controller {
|
||||
);
|
||||
}
|
||||
|
||||
private async getTimedFrontendApiFeatures(req, context) {
|
||||
const stopTimer = this.timer('getFrontendApiFeatures');
|
||||
const features =
|
||||
await this.services.frontendApiService.getFrontendApiFeatures(
|
||||
req.user,
|
||||
context,
|
||||
);
|
||||
stopTimer();
|
||||
return features;
|
||||
}
|
||||
|
||||
private async getTimedNewFrontendApiFeatures(req, context) {
|
||||
const stopTimer = this.timer('getNewFrontendApiFeatures');
|
||||
const features =
|
||||
await this.services.frontendApiService.getNewFrontendApiFeatures(
|
||||
req.user,
|
||||
context,
|
||||
);
|
||||
stopTimer();
|
||||
return features;
|
||||
}
|
||||
|
||||
private async registerFrontendApiMetrics(
|
||||
req: ApiUserRequest<unknown, unknown, ClientMetricsSchema>,
|
||||
res: Response,
|
||||
|
@ -49,7 +49,6 @@ export class FrontendApiRepository
|
||||
}
|
||||
|
||||
getToggle(name: string): FeatureInterface {
|
||||
//@ts-ignore (we must update the node SDK to allow undefined)
|
||||
return this.globalFrontendApiCache.getToggle(name, this.token);
|
||||
}
|
||||
|
||||
|
@ -1,155 +0,0 @@
|
||||
import {
|
||||
type IApiUser,
|
||||
type IUnleashConfig,
|
||||
type IUnleashStores,
|
||||
TEST_AUDIT_USER,
|
||||
} from '../../types';
|
||||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||
import type { FrontendApiService } from './frontend-api-service';
|
||||
import { createFrontendApiService } from './createFrontendApiService';
|
||||
import { createLastSeenService } from '../metrics/last-seen/createLastSeenService';
|
||||
import ClientMetricsServiceV2 from '../metrics/client-metrics/metrics-service-v2';
|
||||
import ConfigurationRevisionService from '../feature-toggle/configuration-revision-service';
|
||||
import getLogger from '../../../test/fixtures/no-logger';
|
||||
import { createTestConfig } from '../../../test/config/test-config';
|
||||
import { ApiTokenType } from '../../types/models/api-token';
|
||||
import type FeatureToggleService from '../feature-toggle/feature-toggle-service';
|
||||
import { createFeatureToggleService } from '../feature-toggle/createFeatureToggleService';
|
||||
import {
|
||||
FRONTEND_API_REPOSITORY_CREATED,
|
||||
PROXY_REPOSITORY_CREATED,
|
||||
} from '../../metric-events';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db: ITestDb;
|
||||
let frontendApiService: FrontendApiService;
|
||||
let featureToggleService: FeatureToggleService;
|
||||
let configurationRevisionService: ConfigurationRevisionService;
|
||||
let config: IUnleashConfig;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('frontend_api_service', getLogger);
|
||||
stores = db.stores;
|
||||
config = createTestConfig({
|
||||
experimental: {
|
||||
flags: {
|
||||
globalFrontendApiCache: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const lastSeenService = createLastSeenService(db.rawDatabase, config);
|
||||
const clientMetricsServiceV2 = new ClientMetricsServiceV2(
|
||||
stores,
|
||||
config,
|
||||
lastSeenService,
|
||||
);
|
||||
configurationRevisionService = ConfigurationRevisionService.getInstance(
|
||||
stores,
|
||||
config,
|
||||
);
|
||||
frontendApiService = createFrontendApiService(
|
||||
db.rawDatabase,
|
||||
config,
|
||||
clientMetricsServiceV2,
|
||||
configurationRevisionService,
|
||||
);
|
||||
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
const createProject = async (project: string) => {
|
||||
await stores.projectStore.create({
|
||||
name: project,
|
||||
description: '',
|
||||
id: project,
|
||||
});
|
||||
await stores.projectStore.addEnvironmentToProject(project, 'development');
|
||||
await stores.projectStore.addEnvironmentToProject(project, 'production');
|
||||
};
|
||||
|
||||
const createEnvironment = async (environment: string) => {
|
||||
await stores.environmentStore.create({ name: environment, type: 'test' });
|
||||
};
|
||||
|
||||
const createFeature = async (project: string, featureName: string) => {
|
||||
await featureToggleService.createFeatureToggle(
|
||||
project,
|
||||
{ name: featureName, description: '' },
|
||||
TEST_AUDIT_USER,
|
||||
);
|
||||
};
|
||||
|
||||
const enableFeature = async (
|
||||
project: string,
|
||||
featureName: string,
|
||||
environment: string,
|
||||
) => {
|
||||
await featureToggleService.unprotectedUpdateEnabled(
|
||||
project,
|
||||
featureName,
|
||||
environment,
|
||||
true,
|
||||
TEST_AUDIT_USER,
|
||||
);
|
||||
};
|
||||
|
||||
test('Compare Frontend API implementations', async () => {
|
||||
const projectA = 'projectA';
|
||||
const projectB = 'projectB';
|
||||
|
||||
await createEnvironment('development');
|
||||
await createEnvironment('production');
|
||||
|
||||
await createProject(projectA);
|
||||
await createProject(projectB);
|
||||
|
||||
await createFeature(projectA, 'featureA'); // include
|
||||
await createFeature(projectA, 'featureB'); // another env
|
||||
await createFeature(projectA, 'featureC'); // not enabled
|
||||
await createFeature(projectA, 'featureD'); // include
|
||||
await enableFeature(projectA, 'featureA', 'development');
|
||||
await enableFeature(projectA, 'featureD', 'development');
|
||||
await enableFeature(projectA, 'featureB', 'production');
|
||||
|
||||
await createFeature(projectB, 'featureE'); // another project
|
||||
await enableFeature(projectB, 'featureE', 'development');
|
||||
|
||||
await configurationRevisionService.updateMaxRevisionId();
|
||||
|
||||
let proxyRepositoriesCount = 0;
|
||||
config.eventBus.on(PROXY_REPOSITORY_CREATED, () => {
|
||||
proxyRepositoriesCount++;
|
||||
});
|
||||
|
||||
let frontendRepositoriesCount = 0;
|
||||
config.eventBus.on(FRONTEND_API_REPOSITORY_CREATED, () => {
|
||||
frontendRepositoriesCount++;
|
||||
});
|
||||
|
||||
const oldFeatures = await frontendApiService.getFrontendApiFeatures(
|
||||
{
|
||||
projects: [projectA],
|
||||
environment: 'development',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
} as IApiUser,
|
||||
{ sessionId: '1234' },
|
||||
);
|
||||
|
||||
const newFeatures = await frontendApiService.getNewFrontendApiFeatures(
|
||||
{
|
||||
projects: [projectA],
|
||||
environment: 'development',
|
||||
type: ApiTokenType.FRONTEND,
|
||||
} as IApiUser,
|
||||
{ sessionId: '1234' },
|
||||
);
|
||||
|
||||
expect(proxyRepositoriesCount).toBe(1);
|
||||
expect(frontendRepositoriesCount).toBe(1);
|
||||
expect(oldFeatures).toEqual(newFeatures);
|
||||
expect(newFeatures.length).toBe(2);
|
||||
});
|
@ -52,7 +52,7 @@ test('frontend api service fetching features from global cache', async () => {
|
||||
globalFrontendApiCache,
|
||||
);
|
||||
|
||||
const features = await frontendApiService.getNewFrontendApiFeatures(
|
||||
const features = await frontendApiService.getFrontendApiFeatures(
|
||||
{
|
||||
projects: ['irrelevant'],
|
||||
environment: 'irrelevant',
|
||||
|
@ -25,13 +25,9 @@ import {
|
||||
} from '../../types/settings/frontend-settings';
|
||||
import { validateOrigins } from '../../util';
|
||||
import { BadDataError, InvalidTokenError } from '../../error';
|
||||
import {
|
||||
FRONTEND_API_REPOSITORY_CREATED,
|
||||
PROXY_REPOSITORY_CREATED,
|
||||
} from '../../metric-events';
|
||||
import { FRONTEND_API_REPOSITORY_CREATED } from '../../metric-events';
|
||||
import { FrontendApiRepository } from './frontend-api-repository';
|
||||
import type { GlobalFrontendApiCache } from './global-frontend-api-cache';
|
||||
import { ProxyRepository } from './proxy-repository';
|
||||
|
||||
export type Config = Pick<
|
||||
IUnleashConfig,
|
||||
@ -66,8 +62,6 @@ export class FrontendApiService {
|
||||
*/
|
||||
private readonly clients: Map<ApiUser['secret'], Promise<Unleash>> =
|
||||
new Map();
|
||||
private readonly newClients: Map<ApiUser['secret'], Promise<Unleash>> =
|
||||
new Map();
|
||||
|
||||
private cachedFrontendSettings?: FrontendSettings;
|
||||
|
||||
@ -93,34 +87,6 @@ export class FrontendApiService {
|
||||
const sessionId =
|
||||
context.sessionId || crypto.randomBytes(18).toString('hex');
|
||||
|
||||
const resultDefinitions = definitions
|
||||
.filter((feature) =>
|
||||
client.isEnabled(feature.name, {
|
||||
...context,
|
||||
sessionId,
|
||||
}),
|
||||
)
|
||||
.map((feature) => ({
|
||||
name: feature.name,
|
||||
enabled: Boolean(feature.enabled),
|
||||
variant: client.getVariant(feature.name, {
|
||||
...context,
|
||||
sessionId,
|
||||
}),
|
||||
impressionData: Boolean(feature.impressionData),
|
||||
}));
|
||||
return resultDefinitions;
|
||||
}
|
||||
|
||||
async getNewFrontendApiFeatures(
|
||||
token: IApiUser,
|
||||
context: Context,
|
||||
): Promise<FrontendApiFeatureSchema[]> {
|
||||
const client = await this.newClientForFrontendApiToken(token);
|
||||
const definitions = client.getFeatureToggleDefinitions() || [];
|
||||
const sessionId =
|
||||
context.sessionId || crypto.randomBytes(18).toString('hex');
|
||||
|
||||
const resultDefinitions = definitions
|
||||
.filter((feature) => {
|
||||
const enabled = client.isEnabled(feature.name, {
|
||||
@ -170,56 +136,13 @@ export class FrontendApiService {
|
||||
if (!client) {
|
||||
client = this.createClientForFrontendApiToken(token);
|
||||
this.clients.set(token.secret, client);
|
||||
this.config.eventBus.emit(PROXY_REPOSITORY_CREATED);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async newClientForFrontendApiToken(
|
||||
token: IApiUser,
|
||||
): Promise<Unleash> {
|
||||
FrontendApiService.assertExpectedTokenType(token);
|
||||
|
||||
let newClient = this.newClients.get(token.secret);
|
||||
if (!newClient) {
|
||||
newClient = this.createNewClientForFrontendApiToken(token);
|
||||
this.newClients.set(token.secret, newClient);
|
||||
this.config.eventBus.emit(FRONTEND_API_REPOSITORY_CREATED);
|
||||
}
|
||||
|
||||
return newClient;
|
||||
}
|
||||
|
||||
private async createClientForFrontendApiToken(
|
||||
token: IApiUser,
|
||||
): Promise<Unleash> {
|
||||
const repository = new ProxyRepository(
|
||||
this.config,
|
||||
this.stores,
|
||||
this.services,
|
||||
token,
|
||||
);
|
||||
const client = new Unleash({
|
||||
appName: 'proxy',
|
||||
url: 'unused',
|
||||
storageProvider: new InMemStorageProvider(),
|
||||
disableMetrics: true,
|
||||
repository,
|
||||
disableAutoStart: true,
|
||||
skipInstanceCountWarning: true,
|
||||
});
|
||||
|
||||
client.on(UnleashEvents.Error, (error) => {
|
||||
this.logger.error('We found an event error', error);
|
||||
});
|
||||
|
||||
await client.start();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async createNewClientForFrontendApiToken(
|
||||
private async createClientForFrontendApiToken(
|
||||
token: IApiUser,
|
||||
): Promise<Unleash> {
|
||||
const repository = new FrontendApiRepository(
|
||||
@ -259,6 +182,10 @@ export class FrontendApiService {
|
||||
this.clients.forEach((promise) => promise.then((c) => c.destroy()));
|
||||
}
|
||||
|
||||
refreshData(): Promise<void> {
|
||||
return this.globalFrontendApiCache.refreshData();
|
||||
}
|
||||
|
||||
private static assertExpectedTokenType({ type }: IApiUser) {
|
||||
if (!(type === ApiTokenType.FRONTEND || type === ApiTokenType.ADMIN)) {
|
||||
throw new InvalidTokenError();
|
||||
|
@ -12,18 +12,16 @@ import {
|
||||
} from '../../types/models/api-token';
|
||||
import { startOfHour } from 'date-fns';
|
||||
import {
|
||||
FEATURE_UPDATED,
|
||||
type IConstraint,
|
||||
type IStrategyConfig,
|
||||
SYSTEM_USER_AUDIT,
|
||||
TEST_AUDIT_USER,
|
||||
} from '../../types';
|
||||
import { ProxyRepository } from './index';
|
||||
import type { Logger } from '../../logger';
|
||||
import type { FrontendApiService } from './frontend-api-service';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
const TEST_USER_ID = -9999;
|
||||
let frontendApiService: FrontendApiService;
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('frontend_api', getLogger);
|
||||
app = await setupAppWithAuth(
|
||||
@ -33,6 +31,7 @@ beforeAll(async () => {
|
||||
},
|
||||
db.rawDatabase,
|
||||
);
|
||||
frontendApiService = app.services.frontendApiService;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -161,6 +160,7 @@ test('should allow requests with a token secret alias', async () => {
|
||||
alias: randomId(),
|
||||
environment: envB,
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.expect('Content-Type', /json/)
|
||||
@ -226,6 +226,7 @@ test('should allow requests with an admin token', async () => {
|
||||
projects: ['*'],
|
||||
environment: '*',
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', adminToken.secret)
|
||||
@ -264,6 +265,7 @@ test('should not allow requests with an invalid frontend token', async () => {
|
||||
|
||||
test('should allow requests with a frontend token', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -441,6 +443,7 @@ test('should filter features by enabled/disabled', async () => {
|
||||
enabled: false,
|
||||
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -488,6 +491,7 @@ test('should filter features by strategies', async () => {
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', constraints: [], parameters: {} }],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -544,6 +548,7 @@ test('should filter features by constraints', async () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend?appName=a')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -585,6 +590,7 @@ test('should be able to set environment as a context variable', async () => {
|
||||
],
|
||||
});
|
||||
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend?environment=staging')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -604,7 +610,6 @@ test('should be able to set environment as a context variable', async () => {
|
||||
expect(res.body.toggles).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('should filter features by project', async () => {
|
||||
const projectA = 'projectA';
|
||||
const projectB = 'projectB';
|
||||
@ -636,6 +641,7 @@ test('should filter features by project', async () => {
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', parameters: {} }],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendTokenDefault.secret)
|
||||
@ -768,6 +774,7 @@ test('should filter features by environment', async () => {
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', parameters: {} }],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendTokenEnvironmentDefault.secret)
|
||||
@ -868,6 +875,7 @@ test('should filter features by segment', async () => {
|
||||
await app.services.segmentService.addToStrategy(segmentA.id, strategyA.id);
|
||||
await app.services.segmentService.addToStrategy(segmentB.id, strategyB.id);
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -900,102 +908,6 @@ test('should filter features by segment', async () => {
|
||||
.expect((res) => expect(res.body).toEqual({ toggles: [] }));
|
||||
});
|
||||
|
||||
test('Should sync proxy for keys on an interval', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const user = await app.services.apiTokenService.getUserForToken(
|
||||
frontendToken.secret,
|
||||
);
|
||||
|
||||
const spy = jest.spyOn(
|
||||
ProxyRepository.prototype as any,
|
||||
'featuresForToken',
|
||||
);
|
||||
expect(user).not.toBeNull();
|
||||
const proxyRepository = new ProxyRepository(
|
||||
{
|
||||
getLogger,
|
||||
frontendApi: { refreshIntervalInMs: 5000 },
|
||||
eventBus: <any>{ emit: jest.fn() },
|
||||
},
|
||||
db.stores,
|
||||
app.services,
|
||||
user!,
|
||||
);
|
||||
|
||||
await proxyRepository.start();
|
||||
|
||||
jest.advanceTimersByTime(60000);
|
||||
|
||||
proxyRepository.stop();
|
||||
expect(spy.mock.calls.length > 6).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('Should change fetch interval', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const user = await app.services.apiTokenService.getUserForToken(
|
||||
frontendToken.secret,
|
||||
);
|
||||
|
||||
const spy = jest.spyOn(
|
||||
ProxyRepository.prototype as any,
|
||||
'featuresForToken',
|
||||
);
|
||||
const proxyRepository = new ProxyRepository(
|
||||
{
|
||||
getLogger,
|
||||
frontendApi: { refreshIntervalInMs: 1000 },
|
||||
eventBus: <any>{ emit: jest.fn() },
|
||||
},
|
||||
db.stores,
|
||||
app.services,
|
||||
user!,
|
||||
);
|
||||
|
||||
await proxyRepository.start();
|
||||
|
||||
jest.advanceTimersByTime(60000);
|
||||
|
||||
proxyRepository.stop();
|
||||
expect(spy.mock.calls.length > 30).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('Should not recursively set off timers on events', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const user = await app.services.apiTokenService.getUserForToken(
|
||||
frontendToken.secret,
|
||||
);
|
||||
|
||||
const spy = jest.spyOn(ProxyRepository.prototype as any, 'dataPolling');
|
||||
const proxyRepository = new ProxyRepository(
|
||||
{
|
||||
getLogger,
|
||||
frontendApi: { refreshIntervalInMs: 5000 },
|
||||
eventBus: <any>{ emit: jest.fn() },
|
||||
},
|
||||
db.stores,
|
||||
app.services,
|
||||
user!,
|
||||
);
|
||||
|
||||
await proxyRepository.start();
|
||||
|
||||
db.stores.eventStore.emit(FEATURE_UPDATED);
|
||||
|
||||
jest.advanceTimersByTime(10000);
|
||||
|
||||
proxyRepository.stop();
|
||||
expect(spy.mock.calls.length < 3).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should return maxAge header on options call', async () => {
|
||||
await app.request
|
||||
.options('/api/frontend')
|
||||
@ -1006,51 +918,6 @@ test('should return maxAge header on options call', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should terminate data polling when stop is called', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
const user = await app.services.apiTokenService.getUserForToken(
|
||||
frontendToken.secret,
|
||||
);
|
||||
|
||||
const logTrap: any[] = [];
|
||||
const getDebugLogger = (): Logger => {
|
||||
return {
|
||||
/* eslint-disable-next-line */
|
||||
debug: (message: any, ...args: any[]) => {
|
||||
logTrap.push(message);
|
||||
},
|
||||
/* eslint-disable-next-line */
|
||||
info: (...args: any[]) => {},
|
||||
/* eslint-disable-next-line */
|
||||
warn: (...args: any[]) => {},
|
||||
/* eslint-disable-next-line */
|
||||
error: (...args: any[]) => {},
|
||||
/* eslint-disable-next-line */
|
||||
fatal: (...args: any[]) => {},
|
||||
};
|
||||
};
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
const proxyRepository = new ProxyRepository(
|
||||
{
|
||||
getLogger: getDebugLogger,
|
||||
frontendApi: { refreshIntervalInMs: 1 },
|
||||
eventBus: <any>{ emit: jest.fn() },
|
||||
},
|
||||
db.stores,
|
||||
app.services,
|
||||
user!,
|
||||
);
|
||||
|
||||
await proxyRepository.start();
|
||||
proxyRepository.stop();
|
||||
// Polling here is an async recursive call, so we gotta give it a bit of time
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
expect(logTrap).toContain(
|
||||
'Shutting down data polling for proxy repository',
|
||||
);
|
||||
});
|
||||
|
||||
test('should evaluate strategies when returning toggles', async () => {
|
||||
const frontendToken = await createApiToken(ApiTokenType.FRONTEND);
|
||||
await createFeatureToggle({
|
||||
@ -1084,6 +951,7 @@ test('should evaluate strategies when returning toggles', async () => {
|
||||
],
|
||||
});
|
||||
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -1144,6 +1012,7 @@ test('should not return all features', async () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -1256,6 +1125,7 @@ test('should NOT evaluate disabled strategies when returning toggles', async ()
|
||||
],
|
||||
});
|
||||
|
||||
await frontendApiService.refreshData();
|
||||
await app.request
|
||||
.get('/api/frontend')
|
||||
.set('Authorization', frontendToken.secret)
|
||||
@ -1344,7 +1214,7 @@ test('should resolve variable rollout percentage consistently', async () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await frontendApiService.refreshData();
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const { body } = await app.request
|
||||
.get('/api/frontend')
|
||||
|
@ -110,9 +110,8 @@ export class GlobalFrontendApiCache extends EventEmitter {
|
||||
}
|
||||
|
||||
// TODO: fetch only relevant projects/environments based on tokens
|
||||
private async refreshData() {
|
||||
public async refreshData() {
|
||||
try {
|
||||
const stopTimer = this.timer('refreshData');
|
||||
this.featuresByEnvironment = await this.getAllFeatures();
|
||||
this.segments = await this.getAllSegments();
|
||||
if (this.status === 'starting') {
|
||||
@ -122,7 +121,6 @@ export class GlobalFrontendApiCache extends EventEmitter {
|
||||
this.status = 'updated';
|
||||
this.emit('updated');
|
||||
}
|
||||
stopTimer();
|
||||
} catch (e) {
|
||||
this.logger.error('Cannot load data for token', e);
|
||||
}
|
||||
|
@ -51,8 +51,6 @@ export type IFlagKey =
|
||||
| 'responseTimeMetricsFix'
|
||||
| 'scimApi'
|
||||
| 'displayEdgeBanner'
|
||||
| 'globalFrontendApiCache'
|
||||
| 'returnGlobalFrontendApiCache'
|
||||
| 'projectOverviewRefactor'
|
||||
| 'variantDependencies'
|
||||
| 'disableShowContextFieldSelectionValues'
|
||||
@ -255,14 +253,6 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_RESPONSE_TIME_METRICS_FIX,
|
||||
false,
|
||||
),
|
||||
globalFrontendApiCache: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_GLOBAL_FRONTEND_API_CACHE,
|
||||
false,
|
||||
),
|
||||
returnGlobalFrontendApiCache: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_RETURN_GLOBAL_FRONTEND_API_CACHE,
|
||||
false,
|
||||
),
|
||||
projectOverviewRefactor: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR,
|
||||
false,
|
||||
|
@ -47,8 +47,6 @@ process.nextTick(async () => {
|
||||
executiveDashboardUI: true,
|
||||
userAccessUIEnabled: true,
|
||||
outdatedSdksBanner: true,
|
||||
globalFrontendApiCache: true,
|
||||
returnGlobalFrontendApiCache: false,
|
||||
projectOverviewRefactor: true,
|
||||
disableShowContextFieldSelectionValues: false,
|
||||
variantDependencies: true,
|
||||
|
Loading…
Reference in New Issue
Block a user