mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-08 01:15:49 +02:00
feat: segment delta (#8990)
Now the delta endpoint also always returns all the segments.
This commit is contained in:
parent
37b55eff5a
commit
138ba35d7a
@ -1,8 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
|
IClientSegment,
|
||||||
IEventStore,
|
IEventStore,
|
||||||
IFeatureToggleDeltaQuery,
|
IFeatureToggleDeltaQuery,
|
||||||
IFeatureToggleQuery,
|
IFeatureToggleQuery,
|
||||||
IFlagResolver,
|
IFlagResolver,
|
||||||
|
ISegmentReadModel,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import type ConfigurationRevisionService from '../../feature-toggle/configuration-revision-service';
|
import type ConfigurationRevisionService from '../../feature-toggle/configuration-revision-service';
|
||||||
import { UPDATE_REVISION } from '../../feature-toggle/configuration-revision-service';
|
import { UPDATE_REVISION } from '../../feature-toggle/configuration-revision-service';
|
||||||
@ -21,6 +23,7 @@ export type RevisionDeltaEntry = {
|
|||||||
updated: FeatureConfigurationDeltaClient[];
|
updated: FeatureConfigurationDeltaClient[];
|
||||||
revisionId: number;
|
revisionId: number;
|
||||||
removed: DeletedFeature[];
|
removed: DeletedFeature[];
|
||||||
|
segments: IClientSegment[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Revision = {
|
export type Revision = {
|
||||||
@ -96,6 +99,8 @@ export class ClientFeatureToggleDelta {
|
|||||||
|
|
||||||
private delta: Revisions = {};
|
private delta: Revisions = {};
|
||||||
|
|
||||||
|
private segments: IClientSegment[];
|
||||||
|
|
||||||
private eventStore: IEventStore;
|
private eventStore: IEventStore;
|
||||||
|
|
||||||
private currentRevisionId: number = 0;
|
private currentRevisionId: number = 0;
|
||||||
@ -106,8 +111,11 @@ export class ClientFeatureToggleDelta {
|
|||||||
|
|
||||||
private configurationRevisionService: ConfigurationRevisionService;
|
private configurationRevisionService: ConfigurationRevisionService;
|
||||||
|
|
||||||
|
private readonly segmentReadModel: ISegmentReadModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
clientFeatureToggleDeltaReadModel: IClientFeatureToggleDeltaReadModel,
|
clientFeatureToggleDeltaReadModel: IClientFeatureToggleDeltaReadModel,
|
||||||
|
segmentReadModel: ISegmentReadModel,
|
||||||
eventStore: IEventStore,
|
eventStore: IEventStore,
|
||||||
configurationRevisionService: ConfigurationRevisionService,
|
configurationRevisionService: ConfigurationRevisionService,
|
||||||
flagResolver: IFlagResolver,
|
flagResolver: IFlagResolver,
|
||||||
@ -117,10 +125,12 @@ export class ClientFeatureToggleDelta {
|
|||||||
this.clientFeatureToggleDeltaReadModel =
|
this.clientFeatureToggleDeltaReadModel =
|
||||||
clientFeatureToggleDeltaReadModel;
|
clientFeatureToggleDeltaReadModel;
|
||||||
this.flagResolver = flagResolver;
|
this.flagResolver = flagResolver;
|
||||||
|
this.segmentReadModel = segmentReadModel;
|
||||||
this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this);
|
this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this);
|
||||||
this.delta = {};
|
this.delta = {};
|
||||||
|
|
||||||
this.initRevisionId();
|
this.initRevisionId();
|
||||||
|
this.updateSegments();
|
||||||
this.configurationRevisionService.on(
|
this.configurationRevisionService.on(
|
||||||
UPDATE_REVISION,
|
UPDATE_REVISION,
|
||||||
this.onUpdateRevisionEvent,
|
this.onUpdateRevisionEvent,
|
||||||
@ -146,6 +156,10 @@ export class ClientFeatureToggleDelta {
|
|||||||
if (!hasDelta) {
|
if (!hasDelta) {
|
||||||
await this.initEnvironmentDelta(environment);
|
await this.initEnvironmentDelta(environment);
|
||||||
}
|
}
|
||||||
|
const hasSegments = this.segments;
|
||||||
|
if (!hasSegments) {
|
||||||
|
await this.updateSegments();
|
||||||
|
}
|
||||||
|
|
||||||
// Should get the latest state if revision does not exist or if sdkRevision is not present
|
// Should get the latest state if revision does not exist or if sdkRevision is not present
|
||||||
// We should be able to do this without going to the database by merging revisions from the delta with
|
// We should be able to do this without going to the database by merging revisions from the delta with
|
||||||
@ -162,6 +176,7 @@ export class ClientFeatureToggleDelta {
|
|||||||
revisionId: this.currentRevisionId,
|
revisionId: this.currentRevisionId,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
updated: await this.getClientFeatures({ environment }),
|
updated: await this.getClientFeatures({ environment }),
|
||||||
|
segments: this.segments,
|
||||||
removed: [],
|
removed: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -178,12 +193,18 @@ export class ClientFeatureToggleDelta {
|
|||||||
projects,
|
projects,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.resolve(compressedRevision);
|
const revisionResponse = {
|
||||||
|
...compressedRevision,
|
||||||
|
segments: this.segments,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise.resolve(revisionResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onUpdateRevisionEvent() {
|
private async onUpdateRevisionEvent() {
|
||||||
if (this.flagResolver.isEnabled('deltaApi')) {
|
if (this.flagResolver.isEnabled('deltaApi')) {
|
||||||
await this.listenToRevisionChange();
|
await this.listenToRevisionChange();
|
||||||
|
await this.updateSegments();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,4 +290,8 @@ export class ClientFeatureToggleDelta {
|
|||||||
await this.clientFeatureToggleDeltaReadModel.getAll(query);
|
await this.clientFeatureToggleDeltaReadModel.getAll(query);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateSegments(): Promise<void> {
|
||||||
|
this.segments = await this.segmentReadModel.getActiveForClient();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import ConfigurationRevisionService from '../../feature-toggle/configuration-rev
|
|||||||
import type { IUnleashConfig } from '../../../types';
|
import type { IUnleashConfig } from '../../../types';
|
||||||
import type { Db } from '../../../db/db';
|
import type { Db } from '../../../db/db';
|
||||||
import ClientFeatureToggleDeltaReadModel from './client-feature-toggle-delta-read-model';
|
import ClientFeatureToggleDeltaReadModel from './client-feature-toggle-delta-read-model';
|
||||||
|
import { SegmentReadModel } from '../../segment/segment-read-model';
|
||||||
|
|
||||||
export const createClientFeatureToggleDelta = (
|
export const createClientFeatureToggleDelta = (
|
||||||
db: Db,
|
db: Db,
|
||||||
@ -19,8 +20,11 @@ export const createClientFeatureToggleDelta = (
|
|||||||
const configurationRevisionService =
|
const configurationRevisionService =
|
||||||
ConfigurationRevisionService.getInstance({ eventStore }, config);
|
ConfigurationRevisionService.getInstance({ eventStore }, config);
|
||||||
|
|
||||||
|
const segmentReadModel = new SegmentReadModel(db);
|
||||||
|
|
||||||
const clientFeatureToggleDelta = new ClientFeatureToggleDelta(
|
const clientFeatureToggleDelta = new ClientFeatureToggleDelta(
|
||||||
clientFeatureToggleDeltaReadModel,
|
clientFeatureToggleDeltaReadModel,
|
||||||
|
segmentReadModel,
|
||||||
eventStore,
|
eventStore,
|
||||||
configurationRevisionService,
|
configurationRevisionService,
|
||||||
flagResolver,
|
flagResolver,
|
||||||
|
@ -12,7 +12,17 @@ exports[`should match snapshot from /api/client/features 1`] = `
|
|||||||
"stale": false,
|
"stale": false,
|
||||||
"strategies": [
|
"strategies": [
|
||||||
{
|
{
|
||||||
"constraints": [],
|
"constraints": [
|
||||||
|
{
|
||||||
|
"caseInsensitive": false,
|
||||||
|
"contextName": "appName",
|
||||||
|
"inverted": false,
|
||||||
|
"operator": "IN",
|
||||||
|
"values": [
|
||||||
|
"test",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
"name": "flexibleRollout",
|
"name": "flexibleRollout",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"groupId": "test1",
|
"groupId": "test1",
|
||||||
|
@ -30,7 +30,15 @@ const getApiClientResponse = (project = 'default') => [
|
|||||||
strategies: [
|
strategies: [
|
||||||
{
|
{
|
||||||
name: 'flexibleRollout',
|
name: 'flexibleRollout',
|
||||||
constraints: [],
|
constraints: [
|
||||||
|
{
|
||||||
|
contextName: 'appName',
|
||||||
|
operator: 'IN',
|
||||||
|
values: ['test'],
|
||||||
|
caseInsensitive: false,
|
||||||
|
inverted: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
rollout: '100',
|
rollout: '100',
|
||||||
stickiness: 'default',
|
stickiness: 'default',
|
||||||
@ -82,6 +90,7 @@ const cleanup = async (db: ITestDb, app: IUnleashTest) => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await db.stores.segmentStore.deleteAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupFeatures = async (
|
const setupFeatures = async (
|
||||||
@ -94,10 +103,24 @@ const setupFeatures = async (
|
|||||||
await app.createFeature('test1', project);
|
await app.createFeature('test1', project);
|
||||||
await app.createFeature('test2', project);
|
await app.createFeature('test2', project);
|
||||||
|
|
||||||
|
const { body: segmentBody } = await app.createSegment({
|
||||||
|
name: 'a',
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
contextName: 'appName',
|
||||||
|
operator: 'IN',
|
||||||
|
values: ['test'],
|
||||||
|
caseInsensitive: false,
|
||||||
|
inverted: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
await app.addStrategyToFeatureEnv(
|
await app.addStrategyToFeatureEnv(
|
||||||
{
|
{
|
||||||
name: 'flexibleRollout',
|
name: 'flexibleRollout',
|
||||||
constraints: [],
|
constraints: [],
|
||||||
|
segments: [segmentBody.id],
|
||||||
parameters: {
|
parameters: {
|
||||||
rollout: '100',
|
rollout: '100',
|
||||||
stickiness: 'default',
|
stickiness: 'default',
|
||||||
@ -323,19 +346,3 @@ test('should match snapshot from /api/client/features', async () => {
|
|||||||
|
|
||||||
expect(result.body).toMatchSnapshot();
|
expect(result.body).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
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.updated);
|
|
||||||
});
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
import { CLIENT_METRICS } from './internals';
|
|
||||||
|
|
||||||
const REQUEST_TIME = 'request_time';
|
const REQUEST_TIME = 'request_time';
|
||||||
const DB_TIME = 'db_time';
|
const DB_TIME = 'db_time';
|
||||||
|
@ -34,6 +34,14 @@ export const clientFeaturesDeltaSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
segments: {
|
||||||
|
description:
|
||||||
|
'A list of [Segments](https://docs.getunleash.io/reference/segments) configured for this Unleash instance',
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/clientSegmentSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
schemas: {
|
schemas: {
|
||||||
|
Loading…
Reference in New Issue
Block a user