Skip to content

Commit

Permalink
feat: Compare old results with new frontend api (#6476)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew committed Mar 8, 2024
1 parent 1949d01 commit 8f105f9
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 28 deletions.
1 change: 1 addition & 0 deletions src/lib/__snapshots__/create-config.test.ts.snap
Expand Up @@ -115,6 +115,7 @@ exports[`should create default config 1`] = `
},
},
"filterInvalidClientMetrics": false,
"globalFrontendApiCache": false,
"googleAuthEnabled": false,
"inMemoryScheduledChangeRequests": false,
"increaseUnleashWidth": false,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/proxy/fake-client-feature-toggle-read-model.ts
Expand Up @@ -4,7 +4,7 @@ import { IClientFeatureToggleReadModel } from './client-feature-toggle-read-mode
export default class FakeClientFeatureToggleReadModel
implements IClientFeatureToggleReadModel
{
constructor(private value: Record<string, IFeatureToggleClient[]>) {}
constructor(private value: Record<string, IFeatureToggleClient[]> = {}) {}

getClient(): Promise<Record<string, IFeatureToggleClient[]>> {
return Promise.resolve(this.value);
Expand Down
16 changes: 8 additions & 8 deletions src/lib/proxy/frontend-api-repository.ts
Expand Up @@ -23,20 +23,20 @@ export class FrontendApiRepository

private readonly token: IApiUser;

private globalFrontendApiRepository: GlobalFrontendApiCache;
private globalFrontendApiCache: GlobalFrontendApiCache;

private running: boolean;

constructor(
config: Config,
globalFrontendApiRepository: GlobalFrontendApiCache,
globalFrontendApiCache: GlobalFrontendApiCache,
token: IApiUser,
) {
super();
this.config = config;
this.logger = config.getLogger('frontend-api-repository.ts');
this.token = token;
this.globalFrontendApiRepository = globalFrontendApiRepository;
this.globalFrontendApiCache = globalFrontendApiCache;
}

getTogglesWithSegmentData(): EnhancedFeatureInterface[] {
Expand All @@ -45,18 +45,18 @@ export class FrontendApiRepository
}

getSegment(id: number): Segment | undefined {
return this.globalFrontendApiRepository.getSegment(id);
return this.globalFrontendApiCache.getSegment(id);
}

getToggle(name: string): FeatureInterface {
//@ts-ignore (we must update the node SDK to allow undefined)
return this.globalFrontendApiRepository
.getToggles(this.token)
.find((feature) => feature.name);
return this.getToggles(this.token).find(
(feature) => feature.name === name,
);
}

getToggles(): FeatureInterface[] {
return this.globalFrontendApiRepository.getToggles(this.token);
return this.globalFrontendApiCache.getToggles(this.token);
}

async start(): Promise<void> {
Expand Down
15 changes: 13 additions & 2 deletions src/lib/proxy/global-frontend-api-cache.test.ts
Expand Up @@ -6,7 +6,12 @@ import noLogger from '../../test/fixtures/no-logger';
import { FakeSegmentReadModel } from '../features/segment/fake-segment-read-model';
import FakeClientFeatureToggleReadModel from './fake-client-feature-toggle-read-model';
import EventEmitter from 'events';
import { IApiUser, IFeatureToggleClient, ISegment } from '../types';
import {
IApiUser,
IFeatureToggleClient,
IFlagResolver,
ISegment,
} from '../types';
import { UPDATE_REVISION } from '../features/feature-toggle/configuration-revision-service';

const state = async (
Expand All @@ -33,11 +38,17 @@ const defaultFeature: IFeatureToggleClient = {
};
const defaultSegment = { name: 'segment', id: 1 } as ISegment;

const alwaysOnFlagResolver = {
isEnabled() {
return true;
},
} as unknown as IFlagResolver;

const createCache = (
segment: ISegment = defaultSegment,
features: Record<string, IFeatureToggleClient[]> = {},
) => {
const config = { getLogger: noLogger };
const config = { getLogger: noLogger, flagResolver: alwaysOnFlagResolver };
const segmentReadModel = new FakeSegmentReadModel([segment as ISegment]);
const clientFeatureToggleReadModel = new FakeClientFeatureToggleReadModel(
features,
Expand Down
6 changes: 4 additions & 2 deletions src/lib/proxy/global-frontend-api-cache.ts
Expand Up @@ -13,7 +13,7 @@ import { UPDATE_REVISION } from '../features/feature-toggle/configuration-revisi
import { mapValues } from '../util';
import { IClientFeatureToggleReadModel } from './client-feature-toggle-read-model-type';

type Config = Pick<IUnleashConfig, 'getLogger'>;
type Config = Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>;

export type GlobalFrontendApiCacheState = 'starting' | 'ready' | 'updated';

Expand Down Expand Up @@ -103,7 +103,9 @@ export class GlobalFrontendApiCache extends EventEmitter {
}

private async onUpdateRevisionEvent() {
await this.refreshData();
if (this.config.flagResolver.isEnabled('globalFrontendApiCache')) {
await this.refreshData();
}
}

private environmentNameForToken(token: IApiUser): string {
Expand Down
32 changes: 28 additions & 4 deletions src/lib/routes/proxy-api/index.ts
Expand Up @@ -10,6 +10,7 @@ import {
emptyResponse,
getStandardResponses,
ProxyClientSchema,
ProxyFeatureSchema,
proxyFeaturesSchema,
ProxyFeaturesSchema,
} from '../../openapi';
Expand All @@ -20,6 +21,7 @@ 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';

interface ApiUserRequest<
PARAM = any,
Expand Down Expand Up @@ -173,10 +175,32 @@ export default class FrontendAPIController extends Controller {
if (!this.config.flagResolver.isEnabled('embedProxy')) {
throw new NotFoundError();
}
const toggles = await this.services.proxyService.getProxyFeatures(
req.user,
FrontendAPIController.createContext(req),
);
let toggles: ProxyFeatureSchema[];
let newToggles: ProxyFeatureSchema[];
if (this.config.flagResolver.isEnabled('globalFrontendApiCache')) {
[toggles, newToggles] = await Promise.all([
this.services.proxyService.getProxyFeatures(
req.user,
FrontendAPIController.createContext(req),
),
this.services.proxyService.getNewProxyFeatures(
req.user,
FrontendAPIController.createContext(req),
),
]);
if (!isEqual(toggles, newToggles)) {
this.logger.warn(
'old features and new feature are different',
toggles.length,
newToggles.length,
);
}
} else {
toggles = await this.services.proxyService.getProxyFeatures(
req.user,
FrontendAPIController.createContext(req),
);
}

res.set('Cache-control', 'no-cache');

Expand Down
28 changes: 23 additions & 5 deletions src/lib/services/index.ts
Expand Up @@ -111,6 +111,9 @@ import {
import { InactiveUsersService } from '../users/inactive/inactive-users-service';
import { SegmentReadModel } from '../features/segment/segment-read-model';
import { FakeSegmentReadModel } from '../features/segment/fake-segment-read-model';
import { GlobalFrontendApiCache } from '../proxy/global-frontend-api-cache';
import ClientFeatureToggleReadModel from '../proxy/client-feature-toggle-read-model';
import FakeClientFeatureToggleReadModel from '../proxy/fake-client-feature-toggle-read-model';

export const createServices = (
stores: IUnleashStores,
Expand Down Expand Up @@ -293,12 +296,27 @@ export const createServices = (
? createClientFeatureToggleService(db, config)
: createFakeClientFeatureToggleService(config);

const proxyService = new ProxyService(config, stores, {
featureToggleServiceV2,
clientMetricsServiceV2,
settingService,
const clientFeatureToggleReadModel = db
? new ClientFeatureToggleReadModel(db, config.eventBus)
: new FakeClientFeatureToggleReadModel();
const globalFrontendApiCache = new GlobalFrontendApiCache(
config,
segmentReadModel,
clientFeatureToggleReadModel,
configurationRevisionService,
});
);

const proxyService = new ProxyService(
config,
stores,
{
featureToggleServiceV2,
clientMetricsServiceV2,
settingService,
configurationRevisionService,
},
globalFrontendApiCache,
);

const edgeService = new EdgeService({ apiTokenService }, config);

Expand Down
54 changes: 54 additions & 0 deletions src/lib/services/proxy-service.test.ts
@@ -0,0 +1,54 @@
import { ProxyService, Config } from './proxy-service';
import { GlobalFrontendApiCache } from '../proxy/global-frontend-api-cache';
import { IApiUser } from '../types';
import { FeatureInterface } from 'unleash-client/lib/feature';
import noLogger from '../../test/fixtures/no-logger';
import { ApiTokenType } from '../types/models/api-token';

test('proxy service fetching features from global cache', async () => {
const irrelevant = {} as any;
const globalFrontendApiCache = {
getToggles(_: IApiUser): FeatureInterface[] {
return [
{
name: 'toggleA',
enabled: true,
project: 'projectA',
type: 'release',
variants: [],
strategies: [
{ name: 'default', parameters: [], constraints: [] },
],
},
{
name: 'toggleB',
enabled: false,
project: 'projectA',
type: 'release',
variants: [],
strategies: [
{ name: 'default', parameters: [], constraints: [] },
],
},
];
},
} as GlobalFrontendApiCache;
const proxyService = new ProxyService(
{ getLogger: noLogger } as unknown as Config,
irrelevant,
irrelevant,
globalFrontendApiCache,
);

const features = await proxyService.getNewProxyFeatures(
{
projects: ['irrelevant'],
environment: 'irrelevant',
type: ApiTokenType.FRONTEND,
} as unknown as IApiUser,
{},
);

expect(features).toMatchObject([{ name: 'toggleA' }]);
expect(features).toHaveLength(1);
});

0 comments on commit 8f105f9

Please sign in to comment.