mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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:
		
							parent
							
								
									13fb7c4a63
								
							
						
					
					
						commit
						8bf1b783e9
					
				@ -9,10 +9,8 @@ import type {
 | 
				
			|||||||
import type { Logger } from '../../logger';
 | 
					import type { Logger } from '../../logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { FeatureConfigurationClient } from '../feature-toggle/types/feature-toggle-strategies-store-type';
 | 
					import type { FeatureConfigurationClient } from '../feature-toggle/types/feature-toggle-strategies-store-type';
 | 
				
			||||||
import type {
 | 
					import type { ClientFeatureToggleDelta } from './delta/client-feature-toggle-delta';
 | 
				
			||||||
    RevisionDeltaEntry,
 | 
					import type { ClientFeaturesDeltaSchema } from '../../openapi';
 | 
				
			||||||
    ClientFeatureToggleDelta,
 | 
					 | 
				
			||||||
} from './delta/client-feature-toggle-delta';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ClientFeatureToggleService {
 | 
					export class ClientFeatureToggleService {
 | 
				
			||||||
    private logger: Logger;
 | 
					    private logger: Logger;
 | 
				
			||||||
@ -44,7 +42,7 @@ export class ClientFeatureToggleService {
 | 
				
			|||||||
    async getClientDelta(
 | 
					    async getClientDelta(
 | 
				
			||||||
        revisionId: number | undefined,
 | 
					        revisionId: number | undefined,
 | 
				
			||||||
        query: IFeatureToggleQuery,
 | 
					        query: IFeatureToggleQuery,
 | 
				
			||||||
    ): Promise<RevisionDeltaEntry | undefined> {
 | 
					    ): Promise<ClientFeaturesDeltaSchema | undefined> {
 | 
				
			||||||
        if (this.clientFeatureToggleDelta !== null) {
 | 
					        if (this.clientFeatureToggleDelta !== null) {
 | 
				
			||||||
            return this.clientFeatureToggleDelta.getDelta(revisionId, query);
 | 
					            return this.clientFeatureToggleDelta.getDelta(revisionId, query);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -140,3 +140,37 @@ const syncRevisions = async () => {
 | 
				
			|||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    await app.services.clientFeatureToggleService.clientFeatureToggleDelta.onUpdateRevisionEvent();
 | 
					    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'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -17,8 +17,10 @@ import type { OpenApiService } from '../../../services/openapi-service';
 | 
				
			|||||||
import { NONE } from '../../../types/permissions';
 | 
					import { NONE } from '../../../types/permissions';
 | 
				
			||||||
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
 | 
					import { createResponseSchema } from '../../../openapi/util/create-response-schema';
 | 
				
			||||||
import type { ClientFeatureToggleService } from '../client-feature-toggle-service';
 | 
					import type { ClientFeatureToggleService } from '../client-feature-toggle-service';
 | 
				
			||||||
import type { RevisionDeltaEntry } from './client-feature-toggle-delta';
 | 
					import {
 | 
				
			||||||
import { clientFeaturesDeltaSchema } from '../../../openapi';
 | 
					    type ClientFeaturesDeltaSchema,
 | 
				
			||||||
 | 
					    clientFeaturesDeltaSchema,
 | 
				
			||||||
 | 
					} from '../../../openapi';
 | 
				
			||||||
import type { QueryOverride } from '../client-feature-toggle.controller';
 | 
					import type { QueryOverride } from '../client-feature-toggle.controller';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ClientFeatureToggleDeltaController extends Controller {
 | 
					export default class ClientFeatureToggleDeltaController extends Controller {
 | 
				
			||||||
@ -75,7 +77,7 @@ export default class ClientFeatureToggleDeltaController extends Controller {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async getDelta(
 | 
					    async getDelta(
 | 
				
			||||||
        req: IAuthRequest,
 | 
					        req: IAuthRequest,
 | 
				
			||||||
        res: Response<RevisionDeltaEntry>,
 | 
					        res: Response<ClientFeaturesDeltaSchema>,
 | 
				
			||||||
    ): Promise<void> {
 | 
					    ): Promise<void> {
 | 
				
			||||||
        if (!this.flagResolver.isEnabled('deltaApi')) {
 | 
					        if (!this.flagResolver.isEnabled('deltaApi')) {
 | 
				
			||||||
            throw new NotFoundError();
 | 
					            throw new NotFoundError();
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ import type { FeatureConfigurationClient } from '../../feature-toggle/types/feat
 | 
				
			|||||||
export interface FeatureConfigurationDeltaClient
 | 
					export interface FeatureConfigurationDeltaClient
 | 
				
			||||||
    extends FeatureConfigurationClient {
 | 
					    extends FeatureConfigurationClient {
 | 
				
			||||||
    description: string;
 | 
					    description: string;
 | 
				
			||||||
    impressionData: false;
 | 
					    impressionData: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IClientFeatureToggleDeltaReadModel {
 | 
					export interface IClientFeatureToggleDeltaReadModel {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ import type {
 | 
				
			|||||||
import { CLIENT_DELTA_MEMORY } from '../../../metric-events';
 | 
					import { CLIENT_DELTA_MEMORY } from '../../../metric-events';
 | 
				
			||||||
import type EventEmitter from 'events';
 | 
					import type EventEmitter from 'events';
 | 
				
			||||||
import type { Logger } from '../../../logger';
 | 
					import type { Logger } from '../../../logger';
 | 
				
			||||||
 | 
					import type { ClientFeaturesDeltaSchema } from '../../../openapi';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DeletedFeature = {
 | 
					type DeletedFeature = {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
@ -79,6 +80,7 @@ const filterRevisionByProject = (
 | 
				
			|||||||
        (feature) =>
 | 
					        (feature) =>
 | 
				
			||||||
            projects.includes('*') || projects.includes(feature.project),
 | 
					            projects.includes('*') || projects.includes(feature.project),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return { ...revision, updated, removed };
 | 
					    return { ...revision, updated, removed };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -153,7 +155,7 @@ export class ClientFeatureToggleDelta {
 | 
				
			|||||||
    async getDelta(
 | 
					    async getDelta(
 | 
				
			||||||
        sdkRevisionId: number | undefined,
 | 
					        sdkRevisionId: number | undefined,
 | 
				
			||||||
        query: IFeatureToggleQuery,
 | 
					        query: IFeatureToggleQuery,
 | 
				
			||||||
    ): Promise<RevisionDeltaEntry | undefined> {
 | 
					    ): Promise<ClientFeaturesDeltaSchema | undefined> {
 | 
				
			||||||
        const projects = query.project ? query.project : ['*'];
 | 
					        const projects = query.project ? query.project : ['*'];
 | 
				
			||||||
        const environment = query.environment ? query.environment : 'default';
 | 
					        const environment = query.environment ? query.environment : 'default';
 | 
				
			||||||
        // TODO: filter by tags, what is namePrefix? anything else?
 | 
					        // TODO: filter by tags, what is namePrefix? anything else?
 | 
				
			||||||
@ -181,9 +183,10 @@ export class ClientFeatureToggleDelta {
 | 
				
			|||||||
            projects,
 | 
					            projects,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const revisionResponse = {
 | 
					        const revisionResponse: ClientFeaturesDeltaSchema = {
 | 
				
			||||||
            ...compressedRevision,
 | 
					            ...compressedRevision,
 | 
				
			||||||
            segments: this.segments,
 | 
					            segments: this.segments,
 | 
				
			||||||
 | 
					            removed: compressedRevision.removed.map((feature) => feature.name),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Promise.resolve(revisionResponse);
 | 
					        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() {
 | 
					    public resetDelta() {
 | 
				
			||||||
        this.delta = {};
 | 
					        this.delta = {};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -217,6 +223,7 @@ export class ClientFeatureToggleDelta {
 | 
				
			|||||||
            ...new Set(
 | 
					            ...new Set(
 | 
				
			||||||
                changeEvents
 | 
					                changeEvents
 | 
				
			||||||
                    .filter((event) => event.featureName)
 | 
					                    .filter((event) => event.featureName)
 | 
				
			||||||
 | 
					                    .filter((event) => event.type !== 'feature-archived')
 | 
				
			||||||
                    .map((event) => event.featureName!),
 | 
					                    .map((event) => event.featureName!),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								src/lib/openapi/spec/client-features-delta-schema.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/lib/openapi/spec/client-features-delta-schema.test.ts
									
									
									
									
									
										Normal 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();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user