1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-23 13:46:45 +02:00

Fix/feature events (#924)

This commit is contained in:
Ivar Conradi Østhus 2021-08-26 13:59:11 +02:00 committed by GitHub
parent 8cb147a81f
commit d28df3e3fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 5822 additions and 5644 deletions

View File

@ -108,7 +108,7 @@
"response-time": "^2.3.2", "response-time": "^2.3.2",
"serve-favicon": "^2.5.0", "serve-favicon": "^2.5.0",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",
"unleash-frontend": "4.1.0-beta.5", "unleash-frontend": "4.1.0-beta.6",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -293,6 +293,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
featureToggle.environments = Object.values( featureToggle.environments = Object.values(
featureToggle.environments, featureToggle.environments,
); );
featureToggle.archived = archived;
return featureToggle; return featureToggle;
} }
throw new NotFoundError( throw new NotFoundError(

View File

@ -148,11 +148,17 @@ function eachConsecutiveEvent(events, callback) {
}); });
} }
function addDiffs(events) { const ignoredProps = ['createdAt', 'lastSeenAt', 'environments', 'id'];
const filterProps = (path, key) => {
return ignoredProps.includes(key);
};
function addDiffs(events = []) {
// TODO: no-param-reassign // TODO: no-param-reassign
eachConsecutiveEvent(events, (left, right) => { eachConsecutiveEvent(events, (left, right) => {
if (right) { if (right) {
left.diffs = diff(right.data, left.data); left.diffs = diff(right.data, left.data, filterProps);
left.diffs = left.diffs || []; left.diffs = left.diffs || [];
} else { } else {
left.diffs = null; left.diffs = null;

View File

@ -197,53 +197,39 @@ class FeatureController extends Controller {
updatedFeature.name = featureName; updatedFeature.name = featureName;
const featureToggleExists = await this.featureService2.hasFeature( const projectId = await this.featureService2.getProjectId(
featureName, updatedFeature.name,
); );
if (featureToggleExists) { const value = await featureSchema.validateAsync(updatedFeature);
await this.featureService2.getFeature(featureName);
const projectId = await this.featureService2.getProjectId(
updatedFeature.name,
);
const value = await featureSchema.validateAsync(updatedFeature);
const { enabled } = value;
const updatedToggle = this.featureService2.updateFeatureToggle(
projectId,
value,
userName,
);
await this.featureService2.removeAllStrategiesForEnv(featureName); await this.featureService2.updateFeatureToggle(
let strategies; projectId,
if (updatedFeature.strategies) { value,
strategies = await Promise.all( userName,
updatedFeature.strategies.map(async (s) => );
this.featureService2.createStrategy(
s, await this.featureService2.removeAllStrategiesForEnv(featureName);
projectId,
featureName, if (updatedFeature.strategies) {
), await Promise.all(
updatedFeature.strategies.map(async (s) =>
this.featureService2.createStrategy(
s,
projectId,
featureName,
), ),
); ),
}
await this.featureService2.updateEnabled(
updatedFeature.name,
GLOBAL_ENV,
updatedFeature.enabled,
userName,
); );
res.status(200).json({
...updatedToggle,
enabled,
strategies: strategies || [],
});
} else {
res.status(404)
.json({
error: `Feature with name ${featureName} does not exist`,
})
.end();
} }
await this.featureService2.updateEnabled(
updatedFeature.name,
GLOBAL_ENV,
updatedFeature.enabled,
userName,
);
const feature = await this.getLegacyFeatureToggle(featureName);
res.status(200).json(feature);
} }
// TODO: remove? // TODO: remove?

View File

@ -3,6 +3,7 @@ import { IUnleashStores } from '../types/stores';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { IEventStore } from '../types/stores/event-store'; import { IEventStore } from '../types/stores/event-store';
import { IEvent } from '../types/model'; import { IEvent } from '../types/model';
import { FEATURE_METADATA_UPDATED } from '../types/events';
export default class EventService { export default class EventService {
private logger: Logger; private logger: Logger;
@ -22,7 +23,10 @@ export default class EventService {
} }
async getEventsForToggle(name: string): Promise<IEvent[]> { async getEventsForToggle(name: string): Promise<IEvent[]> {
return this.eventStore.getEventsFilterByType(name); const events = await this.eventStore.getEventsFilterByType(name);
return events.filter(
(e: IEvent) => e.type !== FEATURE_METADATA_UPDATED,
);
} }
} }

View File

@ -14,6 +14,7 @@ import {
FEATURE_STALE_OFF, FEATURE_STALE_OFF,
FEATURE_STALE_ON, FEATURE_STALE_ON,
FEATURE_UPDATED, FEATURE_UPDATED,
FEATURE_METADATA_UPDATED,
} from '../types/events'; } from '../types/events';
import { GLOBAL_ENV } from '../types/environment'; import { GLOBAL_ENV } from '../types/environment';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
@ -31,6 +32,7 @@ import {
FeatureToggle, FeatureToggle,
FeatureToggleDTO, FeatureToggleDTO,
FeatureToggleWithEnvironment, FeatureToggleWithEnvironment,
FeatureToggleWithEnvironmentLegacy,
IFeatureEnvironmentInfo, IFeatureEnvironmentInfo,
IFeatureStrategy, IFeatureStrategy,
IFeatureToggleQuery, IFeatureToggleQuery,
@ -91,6 +93,12 @@ class FeatureToggleServiceV2 {
this.featureEnvironmentStore = featureEnvironmentStore; this.featureEnvironmentStore = featureEnvironmentStore;
} }
/*
TODO after 4.1.0 release:
- add FEATURE_STRATEGY_ADD event
- add FEATURE_STRATEGY_REMOVE event
- add FEATURE_STRATEGY_UPDATE event
*/
async createStrategy( async createStrategy(
strategyConfig: Omit<IStrategyConfig, 'id'>, strategyConfig: Omit<IStrategyConfig, 'id'>,
projectName: string, projectName: string,
@ -251,22 +259,19 @@ class FeatureToggleServiceV2 {
updatedFeature: FeatureToggleDTO, updatedFeature: FeatureToggleDTO,
userName: string, userName: string,
): Promise<FeatureToggle> { ): Promise<FeatureToggle> {
const featureName = updatedFeature.name;
this.logger.info( this.logger.info(
`${userName} updates feature toggle ${updatedFeature.name}`, `${userName} updates feature toggle ${featureName}`,
); );
await this.featureToggleStore.hasFeature(updatedFeature.name);
const featureToggle = await this.featureToggleStore.updateFeature( const featureToggle = await this.featureToggleStore.updateFeature(
projectId, projectId,
updatedFeature, updatedFeature,
); );
const tags = const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
(await this.featureTagStore.getAllTagsForFeature(
updatedFeature.name,
)) || [];
await this.eventStore.store({ await this.eventStore.store({
type: FEATURE_UPDATED, type: FEATURE_METADATA_UPDATED,
createdBy: userName, createdBy: userName,
data: featureToggle, data: featureToggle,
tags, tags,
@ -375,14 +380,13 @@ class FeatureToggleServiceV2 {
); );
feature.stale = isStale; feature.stale = isStale;
await this.featureToggleStore.updateFeature(feature.project, feature); await this.featureToggleStore.updateFeature(feature.project, feature);
const tags = const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
(await this.featureTagStore.getAllTagsForFeature(featureName)) || const data = await this.getFeatureToggleLegacy(featureName);
[];
await this.eventStore.store({ await this.eventStore.store({
type: isStale ? FEATURE_STALE_ON : FEATURE_STALE_OFF, type: isStale ? FEATURE_STALE_ON : FEATURE_STALE_OFF,
createdBy: userName, createdBy: userName,
data: feature, data,
tags, tags,
}); });
return feature; return feature;
@ -413,8 +417,7 @@ class FeatureToggleServiceV2 {
featureName, featureName,
); );
if (hasEnvironment) { if (hasEnvironment) {
const newEnabled = await this.featureEnvironmentStore.toggleEnvironmentEnabledStatus(
await this.featureEnvironmentStore.toggleEnvironmentEnabledStatus(
environment, environment,
featureName, featureName,
enabled, enabled,
@ -422,14 +425,13 @@ class FeatureToggleServiceV2 {
const feature = await this.featureToggleStore.getFeatureMetadata( const feature = await this.featureToggleStore.getFeatureMetadata(
featureName, featureName,
); );
const tags = const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
(await this.featureTagStore.getAllTagsForFeature( const data = await this.getFeatureToggleLegacy(featureName);
featureName,
)) || [];
await this.eventStore.store({ await this.eventStore.store({
type: FEATURE_UPDATED, type: FEATURE_UPDATED,
createdBy: userName, createdBy: userName,
data: { ...feature, enabled: newEnabled }, data,
tags, tags,
}); });
return feature; return feature;
@ -459,6 +461,15 @@ class FeatureToggleServiceV2 {
); );
} }
async getFeatureToggleLegacy(featureName: string): Promise<FeatureToggleWithEnvironmentLegacy> {
const feature = await this.featureStrategiesStore.getFeatureToggleAdmin(featureName);
const globalEnv = feature.environments.find(e => e.name === GLOBAL_ENV);
const strategies = globalEnv?.strategies || [];
const enabled = globalEnv?.enabled || false;
return {...feature, enabled, strategies };
}
// @deprecated // @deprecated
async updateField( async updateField(
featureName: string, featureName: string,
@ -473,14 +484,16 @@ class FeatureToggleServiceV2 {
); );
feature[field] = value; feature[field] = value;
await this.featureToggleStore.updateFeature(feature.project, feature); await this.featureToggleStore.updateFeature(feature.project, feature);
const tags = const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
(await this.featureTagStore.getAllTagsForFeature(featureName)) ||
[];
// Workaround to support pre 4.1 format
const data = await this.getFeatureToggleLegacy(featureName);
await this.eventStore.store({ await this.eventStore.store({
type: event || FEATURE_UPDATED, type: event || FEATURE_UPDATED,
createdBy: userName, createdBy: userName,
data: feature, data,
tags, tags,
}); });
return feature; return feature;

View File

@ -2,6 +2,7 @@ export const APPLICATION_CREATED = 'application-created';
export const FEATURE_CREATED = 'feature-created'; export const FEATURE_CREATED = 'feature-created';
export const FEATURE_DELETED = 'feature-deleted'; export const FEATURE_DELETED = 'feature-deleted';
export const FEATURE_UPDATED = 'feature-updated'; export const FEATURE_UPDATED = 'feature-updated';
export const FEATURE_METADATA_UPDATED = 'feature-metadata-updated';
export const FEATURE_PROJECT_CHANGE = 'feature-project-change'; export const FEATURE_PROJECT_CHANGE = 'feature-project-change';
export const FEATURE_ARCHIVED = 'feature-archived'; export const FEATURE_ARCHIVED = 'feature-archived';
export const FEATURE_REVIVED = 'feature-revived'; export const FEATURE_REVIVED = 'feature-revived';

View File

@ -63,6 +63,13 @@ export interface FeatureToggleWithEnvironment extends FeatureToggle {
environments: IEnvironmentDetail[]; environments: IEnvironmentDetail[];
} }
// @deprecated
export interface FeatureToggleWithEnvironmentLegacy
extends FeatureToggleWithEnvironment {
strategies: IStrategyConfig[];
enabled: boolean;
}
export interface IEnvironmentDetail extends IEnvironmentOverview { export interface IEnvironmentDetail extends IEnvironmentOverview {
strategies: IStrategyConfig[]; strategies: IStrategyConfig[];
} }

View File

@ -39,7 +39,7 @@ export interface IFeatureStrategiesStore
getStrategiesForEnv(environment: string): Promise<IFeatureStrategy[]>; getStrategiesForEnv(environment: string): Promise<IFeatureStrategy[]>;
getFeatureToggleAdmin( getFeatureToggleAdmin(
featureName: string, featureName: string,
archived: boolean, archived?: boolean,
): Promise<FeatureToggleWithEnvironment>; ): Promise<FeatureToggleWithEnvironment>;
getFeatures( getFeatures(
featureQuery: Partial<IFeatureToggleQuery>, featureQuery: Partial<IFeatureToggleQuery>,

View File

@ -1,11 +1,15 @@
import EventService from '../../../lib/services/event-service';
import { FEATURE_UPDATED } from '../../../lib/types/events';
import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service-v2'; import FeatureToggleServiceV2 from '../../../lib/services/feature-toggle-service-v2';
import { IStrategyConfig } from '../../../lib/types/model'; import { IStrategyConfig } from '../../../lib/types/model';
import { createTestConfig } from '../../config/test-config'; import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init'; import dbInit from '../helpers/database-init';
import { GLOBAL_ENV } from '../../../lib/types/environment';
let stores; let stores;
let db; let db;
let service: FeatureToggleServiceV2; let service: FeatureToggleServiceV2;
let eventService: EventService;
beforeAll(async () => { beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
@ -15,6 +19,7 @@ beforeAll(async () => {
); );
stores = db.stores; stores = db.stores;
service = new FeatureToggleServiceV2(stores, config); service = new FeatureToggleServiceV2(stores, config);
eventService = new EventService(stores, config);
}); });
afterAll(async () => { afterAll(async () => {
@ -74,6 +79,32 @@ test('Should be able to update existing strategy configuration', async () => {
expect(updatedConfig.parameters).toEqual({ b2b: true }); expect(updatedConfig.parameters).toEqual({ b2b: true });
}); });
test('Should include legacy props in event log when updating strategy configuration', async () => {
const userName = 'event-tester';
const featureName = 'update-existing-strategy-events';
const config: Omit<IStrategyConfig, 'id'> = {
name: 'default',
constraints: [],
parameters: {},
};
await service.createFeatureToggle(
'default',
{
name: featureName,
},
userName,
);
await service.createStrategy(config, 'default', featureName);
await service.updateEnabled(featureName, GLOBAL_ENV, true, userName);
const events = await eventService.getEventsForToggle(featureName);
expect(events[0].type).toBe(FEATURE_UPDATED);
expect(events[0].data.enabled).toBe(true);
expect(events[0].data.strategies).toBeDefined();
});
test('Should be able to get strategy by id', async () => { test('Should be able to get strategy by id', async () => {
const config: Omit<IStrategyConfig, 'id'> = { const config: Omit<IStrategyConfig, 'id'> = {
name: 'default', name: 'default',

11277
yarn.lock

File diff suppressed because it is too large Load Diff