Skip to content

Commit

Permalink
feat: segment implementation in delta (#9148)
Browse files Browse the repository at this point in the history
This is implementing the segments events for delta API. Previous version
of delta API, we were just sending all of the segments. Now we will have
`segment-updated` and `segment-removed `events coming to SDK.
  • Loading branch information
sjaanus authored Jan 28, 2025
1 parent 4d582aa commit d993b19
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
} from '../../internals';
import isEqual from 'lodash.isequal';
import { diff } from 'json-diff';
import type { DeltaHydrationEvent } from './delta/client-feature-toggle-delta-types';

const version = 2;

Expand Down Expand Up @@ -193,8 +192,7 @@ export default class FeatureController extends Controller {
a.name.localeCompare(b.name),
);
if (delta?.events[0].type === 'hydration') {
const hydrationEvent: DeltaHydrationEvent =
delta?.events[0];
const hydrationEvent = delta?.events[0];
const sortedNewToggles = hydrationEvent.features.sort(
(a, b) => a.name.localeCompare(b.name),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} 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;
Expand Down Expand Up @@ -121,7 +122,7 @@ test('should return correct delta after feature created', async () => {
expect(body).toMatchObject({
events: [
{
type: 'hydration',
type: DELTA_EVENT_TYPES.HYDRATION,
features: [
{
name: 'base_feature',
Expand All @@ -134,8 +135,6 @@ test('should return correct delta after feature created', async () => {
await app.createFeature('new_feature');

await syncRevisions();
//@ts-ignore
await app.services.clientFeatureToggleService.clientFeatureToggleDelta.onUpdateRevisionEvent();

const { body: deltaBody } = await app.request
.get('/api/client/delta')
Expand All @@ -145,13 +144,7 @@ test('should return correct delta after feature created', async () => {
expect(deltaBody).toMatchObject({
events: [
{
type: 'feature-updated',
feature: {
name: 'new_feature',
},
},
{
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: {
name: 'new_feature',
},
Expand All @@ -161,7 +154,9 @@ test('should return correct delta after feature created', async () => {
});

const syncRevisions = async () => {
await app.services.configurationRevisionService.updateMaxRevisionId();
await app.services.configurationRevisionService.updateMaxRevisionId(false);
//@ts-ignore
await app.services.clientFeatureToggleService.clientFeatureToggleDelta.onUpdateRevisionEvent();
};

test('archived features should not be returned as updated', async () => {
Expand All @@ -187,7 +182,6 @@ test('archived features should not be returned as updated', async () => {
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

Expand All @@ -199,15 +193,80 @@ test('archived features should not be returned as updated', async () => {
expect(deltaBody).toMatchObject({
events: [
{
type: 'feature-removed',
type: DELTA_EVENT_TYPES.FEATURE_REMOVED,
featureName: 'base_feature',
},
{
type: 'feature-updated',
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,
},
],
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type DeltaHydrationEvent = {
eventId: number;
type: 'hydration';
features: ClientFeatureSchema[];
segments: IClientSegment[];
};

export type DeltaEvent =
Expand Down Expand Up @@ -50,14 +51,11 @@ export const isDeltaFeatureRemovedEvent = (
return event.type === DELTA_EVENT_TYPES.FEATURE_REMOVED;
};

export const isDeltaSegmentUpdatedEvent = (
export const isDeltaSegmentEvent = (
event: DeltaEvent,
): event is Extract<DeltaEvent, { type: 'segment-updated' }> => {
return event.type === DELTA_EVENT_TYPES.SEGMENT_UPDATED;
};

export const isDeltaSegmentRemovedEvent = (
event: DeltaEvent,
): event is Extract<DeltaEvent, { type: 'segment-removed' }> => {
return event.type === DELTA_EVENT_TYPES.SEGMENT_REMOVED;
return (
event.type === DELTA_EVENT_TYPES.SEGMENT_UPDATED ||
event.type === DELTA_EVENT_TYPES.SEGMENT_REMOVED
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
filterEventsByQuery,
filterHydrationEventByQuery,
} from './client-feature-toggle-delta';
import type { DeltaHydrationEvent } from './client-feature-toggle-delta-types';
import {
DELTA_EVENT_TYPES,
type DeltaHydrationEvent,
} from './client-feature-toggle-delta-types';

const mockAdd = (params): any => {
const base = {
Expand All @@ -25,12 +28,12 @@ test('revision equal to the base case returns only later revisions ', () => {
const revisionList: DeltaEvent[] = [
{
eventId: 2,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature4' }),
},
{
eventId: 3,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature5' }),
},
];
Expand All @@ -40,12 +43,12 @@ test('revision equal to the base case returns only later revisions ', () => {
expect(revisions).toEqual([
{
eventId: 2,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature4' }),
},
{
eventId: 3,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature5' }),
},
]);
Expand All @@ -55,17 +58,17 @@ test('project filter removes features not in project and nameprefix', () => {
const revisionList: DeltaEvent[] = [
{
eventId: 1,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature1', project: 'project1' }),
},
{
eventId: 2,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'feature2', project: 'project2' }),
},
{
eventId: 3,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'ffeature1', project: 'project1' }),
},
];
Expand All @@ -75,7 +78,7 @@ test('project filter removes features not in project and nameprefix', () => {
expect(revisions).toEqual([
{
eventId: 3,
type: 'feature-updated',
type: DELTA_EVENT_TYPES.FEATURE_UPDATED,
feature: mockAdd({ name: 'ffeature1', project: 'project1' }),
},
]);
Expand All @@ -85,6 +88,13 @@ 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' }),
Expand All @@ -101,6 +111,13 @@ test('project filter removes features not in project in hydration', () => {
expect(revisions).toEqual({
eventId: 1,
type: 'hydration',
segments: [
{
name: 'test',
constraints: [],
id: 1,
},
],
features: [mockAdd({ name: 'myfeature2', project: 'project2' })],
});
});
Loading

0 comments on commit d993b19

Please sign in to comment.