1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

fix: delta do not return archived as changed (#9062)

Our delta API was returning archived feature as updated. Now making sure
we do not put `archived-feature `event into `updated` event array.
Also stop returning removed as complex object.
This commit is contained in:
Jaanus Sellin 2025-01-06 14:48:30 +02:00 committed by GitHub
parent 13fb7c4a63
commit 8bf1b783e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 11 deletions

View File

@ -9,10 +9,8 @@ import type {
import type { Logger } from '../../logger';
import type { FeatureConfigurationClient } from '../feature-toggle/types/feature-toggle-strategies-store-type';
import type {
RevisionDeltaEntry,
ClientFeatureToggleDelta,
} from './delta/client-feature-toggle-delta';
import type { ClientFeatureToggleDelta } from './delta/client-feature-toggle-delta';
import type { ClientFeaturesDeltaSchema } from '../../openapi';
export class ClientFeatureToggleService {
private logger: Logger;
@ -44,7 +42,7 @@ export class ClientFeatureToggleService {
async getClientDelta(
revisionId: number | undefined,
query: IFeatureToggleQuery,
): Promise<RevisionDeltaEntry | undefined> {
): Promise<ClientFeaturesDeltaSchema | undefined> {
if (this.clientFeatureToggleDelta !== null) {
return this.clientFeatureToggleDelta.getDelta(revisionId, query);
} else {

View File

@ -140,3 +140,37 @@ const syncRevisions = async () => {
// @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 } = await app.request.get('/api/client/delta').expect(200);
const currentRevisionId = body.revisionId;
expect(body).toMatchObject({
updated: [
{
name: 'base_feature',
},
],
});
await app.archiveFeature('base_feature');
await app.createFeature('new_feature');
await syncRevisions();
const { body: deltaBody } = await app.request
.get('/api/client/delta')
.set('If-None-Match', currentRevisionId)
.expect(200);
expect(deltaBody).toMatchObject({
updated: [
{
name: 'new_feature',
},
],
removed: ['base_feature'],
});
});

View File

@ -17,8 +17,10 @@ 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 { RevisionDeltaEntry } from './client-feature-toggle-delta';
import { clientFeaturesDeltaSchema } from '../../../openapi';
import {
type ClientFeaturesDeltaSchema,
clientFeaturesDeltaSchema,
} from '../../../openapi';
import type { QueryOverride } from '../client-feature-toggle.controller';
export default class ClientFeatureToggleDeltaController extends Controller {
@ -75,7 +77,7 @@ export default class ClientFeatureToggleDeltaController extends Controller {
async getDelta(
req: IAuthRequest,
res: Response<RevisionDeltaEntry>,
res: Response<ClientFeaturesDeltaSchema>,
): Promise<void> {
if (!this.flagResolver.isEnabled('deltaApi')) {
throw new NotFoundError();

View File

@ -4,7 +4,7 @@ import type { FeatureConfigurationClient } from '../../feature-toggle/types/feat
export interface FeatureConfigurationDeltaClient
extends FeatureConfigurationClient {
description: string;
impressionData: false;
impressionData: boolean;
}
export interface IClientFeatureToggleDeltaReadModel {

View File

@ -17,6 +17,7 @@ import type {
import { CLIENT_DELTA_MEMORY } from '../../../metric-events';
import type EventEmitter from 'events';
import type { Logger } from '../../../logger';
import type { ClientFeaturesDeltaSchema } from '../../../openapi';
type DeletedFeature = {
name: string;
@ -79,6 +80,7 @@ const filterRevisionByProject = (
(feature) =>
projects.includes('*') || projects.includes(feature.project),
);
return { ...revision, updated, removed };
};
@ -153,7 +155,7 @@ export class ClientFeatureToggleDelta {
async getDelta(
sdkRevisionId: number | undefined,
query: IFeatureToggleQuery,
): Promise<RevisionDeltaEntry | undefined> {
): Promise<ClientFeaturesDeltaSchema | undefined> {
const projects = query.project ? query.project : ['*'];
const environment = query.environment ? query.environment : 'default';
// TODO: filter by tags, what is namePrefix? anything else?
@ -181,9 +183,10 @@ export class ClientFeatureToggleDelta {
projects,
);
const revisionResponse = {
const revisionResponse: ClientFeaturesDeltaSchema = {
...compressedRevision,
segments: this.segments,
removed: compressedRevision.removed.map((feature) => feature.name),
};
return Promise.resolve(revisionResponse);
@ -197,6 +200,9 @@ export class ClientFeatureToggleDelta {
}
}
/**
* This is used in client-feature-delta-api.e2e.test.ts, do not remove
*/
public resetDelta() {
this.delta = {};
}
@ -217,6 +223,7 @@ export class ClientFeatureToggleDelta {
...new Set(
changeEvents
.filter((event) => event.featureName)
.filter((event) => event.type !== 'feature-archived')
.map((event) => event.featureName!),
),
];

View File

@ -0,0 +1,27 @@
import { validateSchema } from '../validate';
import type { ClientFeaturesDeltaSchema } from './client-features-delta-schema';
test('clientFeaturesDeltaSchema all fields', () => {
const data: ClientFeaturesDeltaSchema = {
revisionId: 6,
updated: [
{
impressionData: false,
enabled: false,
name: 'base_feature',
description: null,
project: 'default',
stale: false,
type: 'release',
variants: [],
strategies: [],
},
],
removed: [],
segments: [],
};
expect(
validateSchema('#/components/schemas/clientFeaturesDeltaSchema', data),
).toBeUndefined();
});