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:
parent
8cb147a81f
commit
d28df3e3fa
@ -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": {
|
||||
|
@ -293,6 +293,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
featureToggle.environments = Object.values(
|
||||
featureToggle.environments,
|
||||
);
|
||||
featureToggle.archived = archived;
|
||||
return featureToggle;
|
||||
}
|
||||
throw new NotFoundError(
|
||||
|
@ -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;
|
||||
|
@ -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?
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user