mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
chore: pull delta controller out of OSS (#9206)
We are moving delta controller to enterprise. This sets it up.
This commit is contained in:
parent
c85c687816
commit
640db0c057
@ -1,323 +0,0 @@
|
|||||||
import dbInit, {
|
|
||||||
type ITestDb,
|
|
||||||
} from '../../../../test/e2e/helpers/database-init';
|
|
||||||
import {
|
|
||||||
type IUnleashTest,
|
|
||||||
setupAppWithCustomConfig,
|
|
||||||
} from '../../../../test/e2e/helpers/test-helper';
|
|
||||||
import getLogger from '../../../../test/fixtures/no-logger';
|
|
||||||
import { DEFAULT_ENV } from '../../../util/constants';
|
|
||||||
import { DELTA_EVENT_TYPES } from './client-feature-toggle-delta-types';
|
|
||||||
|
|
||||||
let app: IUnleashTest;
|
|
||||||
let db: ITestDb;
|
|
||||||
|
|
||||||
const setupFeatures = async (
|
|
||||||
db: ITestDb,
|
|
||||||
app: IUnleashTest,
|
|
||||||
project = 'default',
|
|
||||||
) => {
|
|
||||||
await app.createFeature('test1', project);
|
|
||||||
await app.createFeature('test2', project);
|
|
||||||
|
|
||||||
await app.addStrategyToFeatureEnv(
|
|
||||||
{
|
|
||||||
name: 'flexibleRollout',
|
|
||||||
constraints: [],
|
|
||||||
parameters: {
|
|
||||||
rollout: '100',
|
|
||||||
stickiness: 'default',
|
|
||||||
groupId: 'test1',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DEFAULT_ENV,
|
|
||||||
'test1',
|
|
||||||
project,
|
|
||||||
);
|
|
||||||
await app.addStrategyToFeatureEnv(
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
constraints: [
|
|
||||||
{
|
|
||||||
contextName: 'userId',
|
|
||||||
operator: 'IN',
|
|
||||||
values: ['123'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
DEFAULT_ENV,
|
|
||||||
'test2',
|
|
||||||
project,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
db = await dbInit('client_feature_toggles_delta', getLogger);
|
|
||||||
app = await setupAppWithCustomConfig(
|
|
||||||
db.stores,
|
|
||||||
{
|
|
||||||
experimental: {
|
|
||||||
flags: {
|
|
||||||
strictSchemaValidation: true,
|
|
||||||
deltaApi: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
db.rawDatabase,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await db.stores.eventStore.deleteAll();
|
|
||||||
await db.stores.featureToggleStore.deleteAll();
|
|
||||||
// @ts-ignore
|
|
||||||
app.services.clientFeatureToggleService.clientFeatureToggleDelta.resetDelta();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await app.destroy();
|
|
||||||
await db.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should match with /api/client/delta', async () => {
|
|
||||||
await setupFeatures(db, app);
|
|
||||||
|
|
||||||
const { body } = await app.request
|
|
||||||
.get('/api/client/features')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const { body: deltaBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(body.features).toMatchObject(deltaBody.events[0].features);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get 304 if asked for latest revision', async () => {
|
|
||||||
await setupFeatures(db, app);
|
|
||||||
|
|
||||||
const { body, headers } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(200);
|
|
||||||
const etag = headers.etag;
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.set('If-None-Match', etag)
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(304);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return correct delta after feature created', async () => {
|
|
||||||
await app.createFeature('base_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
const { body, headers } = await app.request
|
|
||||||
.set('If-None-Match', null)
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(200);
|
|
||||||
const etag = headers.etag;
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.HYDRATION,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: 'base_feature',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.createFeature('new_feature');
|
|
||||||
|
|
||||||
await syncRevisions();
|
|
||||||
|
|
||||||
const { body: deltaBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.set('If-None-Match', etag)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(deltaBody).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: {
|
|
||||||
name: 'new_feature',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const syncRevisions = async () => {
|
|
||||||
await app.services.configurationRevisionService.updateMaxRevisionId(false);
|
|
||||||
//@ts-ignore
|
|
||||||
await app.services.clientFeatureToggleService.clientFeatureToggleDelta.onUpdateRevisionEvent();
|
|
||||||
};
|
|
||||||
|
|
||||||
test('archived features should not be returned as updated', async () => {
|
|
||||||
await app.createFeature('base_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
const { body, headers } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(200);
|
|
||||||
const etag = headers.etag;
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: 'base_feature',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.archiveFeature('base_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
await app.createFeature('new_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
await app.getProjectFeatures('new_feature'); // TODO: this is silly, but events syncing and tests do not work nicely. this is basically a setTimeout
|
|
||||||
|
|
||||||
const { body: deltaBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.set('If-None-Match', etag)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(deltaBody).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_REMOVED,
|
|
||||||
featureName: 'base_feature',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: {
|
|
||||||
name: 'new_feature',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should get segment updated and removed events', async () => {
|
|
||||||
await app.createFeature('base_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
const { body, headers } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(200);
|
|
||||||
const etag = headers.etag;
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.HYDRATION,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: 'base_feature',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { body: segmentBody } = await app.createSegment({
|
|
||||||
name: 'my_segment_a',
|
|
||||||
constraints: [],
|
|
||||||
});
|
|
||||||
// we need this, because revision service does not fire event for segment creation
|
|
||||||
await app.createFeature('not_important1');
|
|
||||||
await syncRevisions();
|
|
||||||
await app.updateSegment(segmentBody.id, {
|
|
||||||
name: 'a',
|
|
||||||
constraints: [],
|
|
||||||
});
|
|
||||||
await syncRevisions();
|
|
||||||
await app.deleteSegment(segmentBody.id);
|
|
||||||
// we need this, because revision service does not fire event for segment deletion
|
|
||||||
await app.createFeature('not_important2');
|
|
||||||
await syncRevisions();
|
|
||||||
|
|
||||||
const { body: deltaBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.set('If-None-Match', etag)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(deltaBody).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.SEGMENT_UPDATED,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.SEGMENT_UPDATED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.SEGMENT_REMOVED,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return hydration if revision not in cache', async () => {
|
|
||||||
await app.createFeature('base_feature');
|
|
||||||
await syncRevisions();
|
|
||||||
const { body, headers } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.expect(200);
|
|
||||||
const etag = headers.etag;
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.HYDRATION,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: 'base_feature',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.createFeature('not_important1');
|
|
||||||
await syncRevisions();
|
|
||||||
|
|
||||||
const { body: deltaBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.set('If-None-Match', etag)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(deltaBody).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { body: rehydrationBody } = await app.request
|
|
||||||
.get('/api/client/delta')
|
|
||||||
.set('If-None-Match', '1')
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(rehydrationBody).toMatchObject({
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
type: DELTA_EVENT_TYPES.HYDRATION,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,190 +0,0 @@
|
|||||||
import type { Response } from 'express';
|
|
||||||
import Controller from '../../../routes/controller';
|
|
||||||
import type {
|
|
||||||
IFlagResolver,
|
|
||||||
IUnleashConfig,
|
|
||||||
IUnleashServices,
|
|
||||||
} from '../../../types';
|
|
||||||
import type { Logger } from '../../../logger';
|
|
||||||
import { querySchema } from '../../../schema/feature-schema';
|
|
||||||
import type { IFeatureToggleQuery } from '../../../types/model';
|
|
||||||
import NotFoundError from '../../../error/notfound-error';
|
|
||||||
import type { IAuthRequest } from '../../../routes/unleash-types';
|
|
||||||
import ApiUser from '../../../types/api-user';
|
|
||||||
import { ALL, isAllProjects } from '../../../types/models/api-token';
|
|
||||||
import type { ClientSpecService } from '../../../services/client-spec-service';
|
|
||||||
import type { OpenApiService } from '../../../services/openapi-service';
|
|
||||||
import { NONE } from '../../../types/permissions';
|
|
||||||
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
|
|
||||||
import type { ClientFeatureToggleService } from '../client-feature-toggle-service';
|
|
||||||
import {
|
|
||||||
type ClientFeaturesDeltaSchema,
|
|
||||||
clientFeaturesDeltaSchema,
|
|
||||||
} from '../../../openapi';
|
|
||||||
import type { QueryOverride } from '../client-feature-toggle.controller';
|
|
||||||
|
|
||||||
export default class ClientFeatureToggleDeltaController extends Controller {
|
|
||||||
private readonly logger: Logger;
|
|
||||||
|
|
||||||
private clientFeatureToggleService: ClientFeatureToggleService;
|
|
||||||
|
|
||||||
private clientSpecService: ClientSpecService;
|
|
||||||
|
|
||||||
private openApiService: OpenApiService;
|
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
{
|
|
||||||
clientFeatureToggleService,
|
|
||||||
clientSpecService,
|
|
||||||
openApiService,
|
|
||||||
}: Pick<
|
|
||||||
IUnleashServices,
|
|
||||||
| 'clientFeatureToggleService'
|
|
||||||
| 'clientSpecService'
|
|
||||||
| 'openApiService'
|
|
||||||
| 'featureToggleService'
|
|
||||||
>,
|
|
||||||
config: IUnleashConfig,
|
|
||||||
) {
|
|
||||||
super(config);
|
|
||||||
this.clientFeatureToggleService = clientFeatureToggleService;
|
|
||||||
this.clientSpecService = clientSpecService;
|
|
||||||
this.openApiService = openApiService;
|
|
||||||
this.flagResolver = config.flagResolver;
|
|
||||||
this.logger = config.getLogger('client-api/delta.js');
|
|
||||||
|
|
||||||
this.route({
|
|
||||||
method: 'get',
|
|
||||||
path: '',
|
|
||||||
handler: this.getDelta,
|
|
||||||
permission: NONE,
|
|
||||||
middleware: [
|
|
||||||
openApiService.validPath({
|
|
||||||
summary: 'Get partial updates (SDK)',
|
|
||||||
description:
|
|
||||||
'Initially returns the full set of feature flags available to the provided API key. When called again with the returned etag, only returns the flags that have changed',
|
|
||||||
operationId: 'getDelta',
|
|
||||||
tags: ['Unstable'],
|
|
||||||
responses: {
|
|
||||||
200: createResponseSchema('clientFeaturesDeltaSchema'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDelta(
|
|
||||||
req: IAuthRequest,
|
|
||||||
res: Response<ClientFeaturesDeltaSchema>,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.flagResolver.isEnabled('deltaApi')) {
|
|
||||||
throw new NotFoundError();
|
|
||||||
}
|
|
||||||
const query = await this.resolveQuery(req);
|
|
||||||
const etag = req.headers['if-none-match'];
|
|
||||||
|
|
||||||
const sanitizedEtag = etag ? etag.replace(/^"(.*)"$/, '$1') : undefined;
|
|
||||||
|
|
||||||
const currentSdkRevisionId = sanitizedEtag
|
|
||||||
? Number.parseInt(sanitizedEtag)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const changedFeatures =
|
|
||||||
await this.clientFeatureToggleService.getClientDelta(
|
|
||||||
currentSdkRevisionId,
|
|
||||||
query,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!changedFeatures) {
|
|
||||||
res.status(304);
|
|
||||||
res.getHeaderNames().forEach((header) => res.removeHeader(header));
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const lastEventId =
|
|
||||||
changedFeatures.events[changedFeatures.events.length - 1].eventId;
|
|
||||||
if (lastEventId === currentSdkRevisionId) {
|
|
||||||
res.status(304);
|
|
||||||
res.getHeaderNames().forEach((header) => res.removeHeader(header));
|
|
||||||
res.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('ETag', `"${lastEventId}"`);
|
|
||||||
this.openApiService.respondWithValidation(
|
|
||||||
200,
|
|
||||||
res,
|
|
||||||
clientFeaturesDeltaSchema.$id,
|
|
||||||
changedFeatures,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async resolveQuery(
|
|
||||||
req: IAuthRequest,
|
|
||||||
): Promise<IFeatureToggleQuery> {
|
|
||||||
const { user, query } = req;
|
|
||||||
|
|
||||||
const override: QueryOverride = {};
|
|
||||||
if (user instanceof ApiUser) {
|
|
||||||
if (!isAllProjects(user.projects)) {
|
|
||||||
override.project = user.projects;
|
|
||||||
}
|
|
||||||
if (user.environment !== ALL) {
|
|
||||||
override.environment = user.environment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inlineSegmentConstraints =
|
|
||||||
!this.clientSpecService.requestSupportsSpec(req, 'segments');
|
|
||||||
|
|
||||||
return this.prepQuery({
|
|
||||||
...query,
|
|
||||||
...override,
|
|
||||||
inlineSegmentConstraints,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
||||||
private paramToArray(param: any) {
|
|
||||||
if (!param) {
|
|
||||||
return param;
|
|
||||||
}
|
|
||||||
return Array.isArray(param) ? param : [param];
|
|
||||||
}
|
|
||||||
|
|
||||||
private async prepQuery({
|
|
||||||
tag,
|
|
||||||
project,
|
|
||||||
namePrefix,
|
|
||||||
environment,
|
|
||||||
inlineSegmentConstraints,
|
|
||||||
}: IFeatureToggleQuery): Promise<IFeatureToggleQuery> {
|
|
||||||
if (
|
|
||||||
!tag &&
|
|
||||||
!project &&
|
|
||||||
!namePrefix &&
|
|
||||||
!environment &&
|
|
||||||
!inlineSegmentConstraints
|
|
||||||
) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagQuery = this.paramToArray(tag);
|
|
||||||
const projectQuery = this.paramToArray(project);
|
|
||||||
const query = await querySchema.validateAsync({
|
|
||||||
tag: tagQuery,
|
|
||||||
project: projectQuery,
|
|
||||||
namePrefix,
|
|
||||||
environment,
|
|
||||||
inlineSegmentConstraints,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (query.tag) {
|
|
||||||
query.tag = query.tag.map((q) => q.split(':'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
import {
|
|
||||||
type DeltaEvent,
|
|
||||||
filterEventsByQuery,
|
|
||||||
filterHydrationEventByQuery,
|
|
||||||
} from './client-feature-toggle-delta';
|
|
||||||
import {
|
|
||||||
DELTA_EVENT_TYPES,
|
|
||||||
type DeltaHydrationEvent,
|
|
||||||
} from './client-feature-toggle-delta-types';
|
|
||||||
|
|
||||||
const mockAdd = (params): any => {
|
|
||||||
const base = {
|
|
||||||
name: 'feature',
|
|
||||||
project: 'default',
|
|
||||||
stale: false,
|
|
||||||
type: 'release',
|
|
||||||
enabled: true,
|
|
||||||
strategies: [],
|
|
||||||
variants: [],
|
|
||||||
description: 'A feature',
|
|
||||||
impressionData: [],
|
|
||||||
dependencies: [],
|
|
||||||
};
|
|
||||||
return { ...base, ...params };
|
|
||||||
};
|
|
||||||
|
|
||||||
test('revision equal to the base case returns only later revisions ', () => {
|
|
||||||
const revisionList: DeltaEvent[] = [
|
|
||||||
{
|
|
||||||
eventId: 2,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature4' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eventId: 3,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature5' }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const revisions = filterEventsByQuery(revisionList, 1, ['default'], '');
|
|
||||||
|
|
||||||
expect(revisions).toEqual([
|
|
||||||
{
|
|
||||||
eventId: 2,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature4' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eventId: 3,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature5' }),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('project filter removes features not in project and nameprefix', () => {
|
|
||||||
const revisionList: DeltaEvent[] = [
|
|
||||||
{
|
|
||||||
eventId: 1,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature1', project: 'project1' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eventId: 2,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'feature2', project: 'project2' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
eventId: 3,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'ffeature1', project: 'project1' }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const revisions = filterEventsByQuery(revisionList, 0, ['project1'], 'ff');
|
|
||||||
|
|
||||||
expect(revisions).toEqual([
|
|
||||||
{
|
|
||||||
eventId: 3,
|
|
||||||
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
|
|
||||||
feature: mockAdd({ name: 'ffeature1', project: 'project1' }),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('project filter removes features not in project in hydration', () => {
|
|
||||||
const revisionList: DeltaHydrationEvent = {
|
|
||||||
eventId: 1,
|
|
||||||
type: 'hydration',
|
|
||||||
segments: [
|
|
||||||
{
|
|
||||||
name: 'test',
|
|
||||||
constraints: [],
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
features: [
|
|
||||||
mockAdd({ name: 'feature1', project: 'project1' }),
|
|
||||||
mockAdd({ name: 'feature2', project: 'project2' }),
|
|
||||||
mockAdd({ name: 'myfeature2', project: 'project2' }),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const revisions = filterHydrationEventByQuery(
|
|
||||||
revisionList,
|
|
||||||
['project2'],
|
|
||||||
'my',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(revisions).toEqual({
|
|
||||||
eventId: 1,
|
|
||||||
type: 'hydration',
|
|
||||||
segments: [
|
|
||||||
{
|
|
||||||
name: 'test',
|
|
||||||
constraints: [],
|
|
||||||
id: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
features: [mockAdd({ name: 'myfeature2', project: 'project2' })],
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,16 +3,11 @@ import FeatureController from '../../features/client-feature-toggles/client-feat
|
|||||||
import MetricsController from '../../features/metrics/instance/metrics';
|
import MetricsController from '../../features/metrics/instance/metrics';
|
||||||
import RegisterController from '../../features/metrics/instance/register';
|
import RegisterController from '../../features/metrics/instance/register';
|
||||||
import type { IUnleashConfig, IUnleashServices } from '../../types';
|
import type { IUnleashConfig, IUnleashServices } from '../../types';
|
||||||
import ClientFeatureToggleDeltaController from '../../features/client-feature-toggles/delta/client-feature-toggle-delta-controller';
|
|
||||||
|
|
||||||
export default class ClientApi extends Controller {
|
export default class ClientApi extends Controller {
|
||||||
constructor(config: IUnleashConfig, services: IUnleashServices) {
|
constructor(config: IUnleashConfig, services: IUnleashServices) {
|
||||||
super(config);
|
super(config);
|
||||||
|
|
||||||
this.use(
|
|
||||||
'/delta',
|
|
||||||
new ClientFeatureToggleDeltaController(services, config).router,
|
|
||||||
);
|
|
||||||
this.use('/features', new FeatureController(services, config).router);
|
this.use('/features', new FeatureController(services, config).router);
|
||||||
this.use('/metrics', new MetricsController(services, config).router);
|
this.use('/metrics', new MetricsController(services, config).router);
|
||||||
this.use('/register', new RegisterController(services, config).router);
|
this.use('/register', new RegisterController(services, config).router);
|
||||||
|
Loading…
Reference in New Issue
Block a user