1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01: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",
"serve-favicon": "^2.5.0",
"stoppable": "^1.1.0",
"unleash-frontend": "4.1.0-beta.5",
"unleash-frontend": "4.1.0-beta.6",
"uuid": "^8.3.2"
},
"devDependencies": {

View File

@ -293,6 +293,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
featureToggle.environments = Object.values(
featureToggle.environments,
);
featureToggle.archived = archived;
return featureToggle;
}
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
eachConsecutiveEvent(events, (left, right) => {
if (right) {
left.diffs = diff(right.data, left.data);
left.diffs = diff(right.data, left.data, filterProps);
left.diffs = left.diffs || [];
} else {
left.diffs = null;

View File

@ -197,53 +197,39 @@ class FeatureController extends Controller {
updatedFeature.name = featureName;
const featureToggleExists = await this.featureService2.hasFeature(
featureName,
const projectId = await this.featureService2.getProjectId(
updatedFeature.name,
);
if (featureToggleExists) {
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,
);
const value = await featureSchema.validateAsync(updatedFeature);
await this.featureService2.removeAllStrategiesForEnv(featureName);
let strategies;
if (updatedFeature.strategies) {
strategies = await Promise.all(
updatedFeature.strategies.map(async (s) =>
this.featureService2.createStrategy(
s,
projectId,
featureName,
),
await this.featureService2.updateFeatureToggle(
projectId,
value,
userName,
);
await this.featureService2.removeAllStrategiesForEnv(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?

View File

@ -3,6 +3,7 @@ import { IUnleashStores } from '../types/stores';
import { Logger } from '../logger';
import { IEventStore } from '../types/stores/event-store';
import { IEvent } from '../types/model';
import { FEATURE_METADATA_UPDATED } from '../types/events';
export default class EventService {
private logger: Logger;
@ -22,7 +23,10 @@ export default class EventService {
}
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_ON,
FEATURE_UPDATED,
FEATURE_METADATA_UPDATED,
} from '../types/events';
import { GLOBAL_ENV } from '../types/environment';
import NotFoundError from '../error/notfound-error';
@ -31,6 +32,7 @@ import {
FeatureToggle,
FeatureToggleDTO,
FeatureToggleWithEnvironment,
FeatureToggleWithEnvironmentLegacy,
IFeatureEnvironmentInfo,
IFeatureStrategy,
IFeatureToggleQuery,
@ -91,6 +93,12 @@ class FeatureToggleServiceV2 {
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(
strategyConfig: Omit<IStrategyConfig, 'id'>,
projectName: string,
@ -251,22 +259,19 @@ class FeatureToggleServiceV2 {
updatedFeature: FeatureToggleDTO,
userName: string,
): Promise<FeatureToggle> {
const featureName = updatedFeature.name;
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(
projectId,
updatedFeature,
);
const tags =
(await this.featureTagStore.getAllTagsForFeature(
updatedFeature.name,
)) || [];
const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
await this.eventStore.store({
type: FEATURE_UPDATED,
type: FEATURE_METADATA_UPDATED,
createdBy: userName,
data: featureToggle,
tags,
@ -375,14 +380,13 @@ class FeatureToggleServiceV2 {
);
feature.stale = isStale;
await this.featureToggleStore.updateFeature(feature.project, feature);
const tags =
(await this.featureTagStore.getAllTagsForFeature(featureName)) ||
[];
const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
const data = await this.getFeatureToggleLegacy(featureName);
await this.eventStore.store({
type: isStale ? FEATURE_STALE_ON : FEATURE_STALE_OFF,
createdBy: userName,
data: feature,
data,
tags,
});
return feature;
@ -413,8 +417,7 @@ class FeatureToggleServiceV2 {
featureName,
);
if (hasEnvironment) {
const newEnabled =
await this.featureEnvironmentStore.toggleEnvironmentEnabledStatus(
await this.featureEnvironmentStore.toggleEnvironmentEnabledStatus(
environment,
featureName,
enabled,
@ -422,14 +425,13 @@ class FeatureToggleServiceV2 {
const feature = await this.featureToggleStore.getFeatureMetadata(
featureName,
);
const tags =
(await this.featureTagStore.getAllTagsForFeature(
featureName,
)) || [];
const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
const data = await this.getFeatureToggleLegacy(featureName);
await this.eventStore.store({
type: FEATURE_UPDATED,
createdBy: userName,
data: { ...feature, enabled: newEnabled },
data,
tags,
});
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
async updateField(
featureName: string,
@ -473,14 +484,16 @@ class FeatureToggleServiceV2 {
);
feature[field] = value;
await this.featureToggleStore.updateFeature(feature.project, feature);
const tags =
(await this.featureTagStore.getAllTagsForFeature(featureName)) ||
[];
const tags = await this.featureTagStore.getAllTagsForFeature(featureName);
// Workaround to support pre 4.1 format
const data = await this.getFeatureToggleLegacy(featureName);
await this.eventStore.store({
type: event || FEATURE_UPDATED,
createdBy: userName,
data: feature,
data,
tags,
});
return feature;

View File

@ -2,6 +2,7 @@ export const APPLICATION_CREATED = 'application-created';
export const FEATURE_CREATED = 'feature-created';
export const FEATURE_DELETED = 'feature-deleted';
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_ARCHIVED = 'feature-archived';
export const FEATURE_REVIVED = 'feature-revived';

View File

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

View File

@ -39,7 +39,7 @@ export interface IFeatureStrategiesStore
getStrategiesForEnv(environment: string): Promise<IFeatureStrategy[]>;
getFeatureToggleAdmin(
featureName: string,
archived: boolean,
archived?: boolean,
): Promise<FeatureToggleWithEnvironment>;
getFeatures(
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 { IStrategyConfig } from '../../../lib/types/model';
import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init';
import { GLOBAL_ENV } from '../../../lib/types/environment';
let stores;
let db;
let service: FeatureToggleServiceV2;
let eventService: EventService;
beforeAll(async () => {
const config = createTestConfig();
@ -15,6 +19,7 @@ beforeAll(async () => {
);
stores = db.stores;
service = new FeatureToggleServiceV2(stores, config);
eventService = new EventService(stores, config);
});
afterAll(async () => {
@ -74,6 +79,32 @@ test('Should be able to update existing strategy configuration', async () => {
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 () => {
const config: Omit<IStrategyConfig, 'id'> = {
name: 'default',

11277
yarn.lock

File diff suppressed because it is too large Load Diff