1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-12-09 20:04:11 +01:00

refactor: prefer eventService.storeEvent methods (#4830)

https://linear.app/unleash/issue/2-1403/consider-refactoring-the-way-tags-are-fetched-for-the-events

This adds 2 methods to `EventService`:
 - `storeEvent`;
 - `storeEvents`;

This allows us to run event-specific logic inside these methods. In the
case of this PR, this means fetching the feature tags in case the event
contains a `featureName` and there are no tags specified in the event.

This prevents us from having to remember to fetch the tags in order to
store feature-related events except for very specific cases, like the
deletion of a feature - You can't fetch tags for a feature that no
longer exists, so in that case we need to pre-fetch the tags before
deleting the feature.

This also allows us to do any event-specific post-processing to the
event before reaching the DB layer.
In general I think it's also nicer that we reference the event service
instead of the event store directly.

There's a lot of changes and a lot of files touched, but most of it is
boilerplate to inject the `eventService` where needed instead of using
the `eventStore` directly.

Hopefully this will be a better approach than
https://github.com/Unleash/unleash/pull/4729

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
Nuno Góis 2023-09-27 14:23:05 +01:00 committed by GitHub
parent a06037625d
commit 87d9497be9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 777 additions and 539 deletions

View File

@ -5,13 +5,15 @@ import { AccountStore } from '../../db/account-store';
import RoleStore from '../../db/role-store'; import RoleStore from '../../db/role-store';
import EnvironmentStore from '../../db/environment-store'; import EnvironmentStore from '../../db/environment-store';
import { AccessStore } from '../../db/access-store'; import { AccessStore } from '../../db/access-store';
import { AccessService, GroupService } from '../../services'; import { AccessService, EventService, GroupService } from '../../services';
import FakeGroupStore from '../../../test/fixtures/fake-group-store'; import FakeGroupStore from '../../../test/fixtures/fake-group-store';
import FakeEventStore from '../../../test/fixtures/fake-event-store'; import FakeEventStore from '../../../test/fixtures/fake-event-store';
import { FakeAccountStore } from '../../../test/fixtures/fake-account-store'; import { FakeAccountStore } from '../../../test/fixtures/fake-account-store';
import FakeRoleStore from '../../../test/fixtures/fake-role-store'; import FakeRoleStore from '../../../test/fixtures/fake-role-store';
import FakeEnvironmentStore from '../../../test/fixtures/fake-environment-store'; import FakeEnvironmentStore from '../../../test/fixtures/fake-environment-store';
import FakeAccessStore from '../../../test/fixtures/fake-access-store'; import FakeAccessStore from '../../../test/fixtures/fake-access-store';
import FeatureTagStore from '../../db/feature-tag-store';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
export const createAccessService = ( export const createAccessService = (
db: Db, db: Db,
@ -24,9 +26,15 @@ export const createAccessService = (
const roleStore = new RoleStore(db, eventBus, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger);
const environmentStore = new EnvironmentStore(db, eventBus, getLogger); const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
const accessStore = new AccessStore(db, eventBus, getLogger); const accessStore = new AccessStore(db, eventBus, getLogger);
const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
const eventService = new EventService(
{ eventStore, featureTagStore },
config,
);
const groupService = new GroupService( const groupService = new GroupService(
{ groupStore, eventStore, accountStore }, { groupStore, accountStore },
{ getLogger }, { getLogger },
eventService,
); );
return new AccessService( return new AccessService(
@ -46,9 +54,15 @@ export const createFakeAccessService = (
const roleStore = new FakeRoleStore(); const roleStore = new FakeRoleStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const accessStore = new FakeAccessStore(roleStore); const accessStore = new FakeAccessStore(roleStore);
const featureTagStore = new FakeFeatureTagStore();
const eventService = new EventService(
{ eventStore, featureTagStore },
config,
);
const groupService = new GroupService( const groupService = new GroupService(
{ groupStore, eventStore, accountStore }, { groupStore, accountStore },
{ getLogger }, { getLogger },
eventService,
); );
return new AccessService( return new AccessService(

View File

@ -12,6 +12,7 @@ import ContextFieldStore from '../../db/context-field-store';
import FeatureStrategiesStore from '../../db/feature-strategy-store'; import FeatureStrategiesStore from '../../db/feature-strategy-store';
import { import {
ContextService, ContextService,
EventService,
FeatureTagService, FeatureTagService,
StrategyService, StrategyService,
TagTypeService, TagTypeService,
@ -63,36 +64,45 @@ export const createFakeExportImportTogglesService = (
const featureToggleService = createFakeFeatureToggleService(config); const featureToggleService = createFakeFeatureToggleService(config);
const privateProjectChecker = createFakePrivateProjectChecker(); const privateProjectChecker = createFakePrivateProjectChecker();
const eventService = new EventService(
{
eventStore,
featureTagStore,
},
config,
);
const featureTagService = new FeatureTagService( const featureTagService = new FeatureTagService(
{ {
tagStore, tagStore,
featureTagStore, featureTagStore,
eventStore,
featureToggleStore, featureToggleStore,
}, },
{ getLogger }, { getLogger },
eventService,
); );
const contextService = new ContextService( const contextService = new ContextService(
{ {
projectStore, projectStore,
eventStore,
contextFieldStore, contextFieldStore,
featureStrategiesStore, featureStrategiesStore,
}, },
{ getLogger, flagResolver }, { getLogger, flagResolver },
eventService,
privateProjectChecker, privateProjectChecker,
); );
const strategyService = new StrategyService( const strategyService = new StrategyService(
{ strategyStore, eventStore }, { strategyStore },
{ getLogger }, { getLogger },
eventService,
); );
const tagTypeService = new TagTypeService( const tagTypeService = new TagTypeService(
{ tagTypeStore, eventStore }, { tagTypeStore },
{ getLogger }, { getLogger },
eventService,
); );
const exportImportService = new ExportImportService( const exportImportService = new ExportImportService(
{ {
eventStore,
importTogglesStore, importTogglesStore,
featureStrategiesStore, featureStrategiesStore,
contextFieldStore, contextFieldStore,
@ -107,6 +117,7 @@ export const createFakeExportImportTogglesService = (
featureToggleService, featureToggleService,
featureTagService, featureTagService,
accessService, accessService,
eventService,
contextService, contextService,
strategyService, strategyService,
tagTypeService, tagTypeService,
@ -160,36 +171,45 @@ export const createExportImportTogglesService = (
const featureToggleService = createFeatureToggleService(db, config); const featureToggleService = createFeatureToggleService(db, config);
const privateProjectChecker = createPrivateProjectChecker(db, config); const privateProjectChecker = createPrivateProjectChecker(db, config);
const eventService = new EventService(
{
eventStore,
featureTagStore,
},
config,
);
const featureTagService = new FeatureTagService( const featureTagService = new FeatureTagService(
{ {
tagStore, tagStore,
featureTagStore, featureTagStore,
eventStore,
featureToggleStore, featureToggleStore,
}, },
{ getLogger }, { getLogger },
eventService,
); );
const contextService = new ContextService( const contextService = new ContextService(
{ {
projectStore, projectStore,
eventStore,
contextFieldStore, contextFieldStore,
featureStrategiesStore, featureStrategiesStore,
}, },
{ getLogger, flagResolver }, { getLogger, flagResolver },
eventService,
privateProjectChecker, privateProjectChecker,
); );
const strategyService = new StrategyService( const strategyService = new StrategyService(
{ strategyStore, eventStore }, { strategyStore },
{ getLogger }, { getLogger },
eventService,
); );
const tagTypeService = new TagTypeService( const tagTypeService = new TagTypeService(
{ tagTypeStore, eventStore }, { tagTypeStore },
{ getLogger }, { getLogger },
eventService,
); );
const exportImportService = new ExportImportService( const exportImportService = new ExportImportService(
{ {
eventStore,
importTogglesStore, importTogglesStore,
featureStrategiesStore, featureStrategiesStore,
contextFieldStore, contextFieldStore,
@ -204,6 +224,7 @@ export const createExportImportTogglesService = (
featureToggleService, featureToggleService,
featureTagService, featureTagService,
accessService, accessService,
eventService,
contextService, contextService,
strategyService, strategyService,
tagTypeService, tagTypeService,

View File

@ -8,7 +8,6 @@ import {
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { IFeatureTagStore } from '../../types/stores/feature-tag-store'; import { IFeatureTagStore } from '../../types/stores/feature-tag-store';
import { ITagTypeStore } from '../../types/stores/tag-type-store'; import { ITagTypeStore } from '../../types/stores/tag-type-store';
import { IEventStore } from '../../types/stores/event-store';
import { IStrategy } from '../../types/stores/strategy-store'; import { IStrategy } from '../../types/stores/strategy-store';
import { IFeatureToggleStore } from '../../types/stores/feature-toggle-store'; import { IFeatureToggleStore } from '../../types/stores/feature-toggle-store';
import { IFeatureStrategiesStore } from '../../types/stores/feature-strategies-store'; import { IFeatureStrategiesStore } from '../../types/stores/feature-strategies-store';
@ -35,6 +34,7 @@ import { extractUsernameFromUser } from '../../util';
import { import {
AccessService, AccessService,
ContextService, ContextService,
EventService,
FeatureTagService, FeatureTagService,
FeatureToggleService, FeatureToggleService,
StrategyService, StrategyService,
@ -57,8 +57,6 @@ export default class ExportImportService {
private featureStrategiesStore: IFeatureStrategiesStore; private featureStrategiesStore: IFeatureStrategiesStore;
private eventStore: IEventStore;
private importTogglesStore: IImportTogglesStore; private importTogglesStore: IImportTogglesStore;
private tagTypeStore: ITagTypeStore; private tagTypeStore: ITagTypeStore;
@ -81,6 +79,8 @@ export default class ExportImportService {
private accessService: AccessService; private accessService: AccessService;
private eventService: EventService;
private tagTypeService: TagTypeService; private tagTypeService: TagTypeService;
private featureTagService: FeatureTagService; private featureTagService: FeatureTagService;
@ -91,7 +91,6 @@ export default class ExportImportService {
stores: Pick< stores: Pick<
IUnleashStores, IUnleashStores,
| 'importTogglesStore' | 'importTogglesStore'
| 'eventStore'
| 'featureStrategiesStore' | 'featureStrategiesStore'
| 'featureToggleStore' | 'featureToggleStore'
| 'featureEnvironmentStore' | 'featureEnvironmentStore'
@ -109,6 +108,7 @@ export default class ExportImportService {
strategyService, strategyService,
contextService, contextService,
accessService, accessService,
eventService,
tagTypeService, tagTypeService,
featureTagService, featureTagService,
}: Pick< }: Pick<
@ -117,11 +117,11 @@ export default class ExportImportService {
| 'strategyService' | 'strategyService'
| 'contextService' | 'contextService'
| 'accessService' | 'accessService'
| 'eventService'
| 'tagTypeService' | 'tagTypeService'
| 'featureTagService' | 'featureTagService'
>, >,
) { ) {
this.eventStore = stores.eventStore;
this.toggleStore = stores.featureToggleStore; this.toggleStore = stores.featureToggleStore;
this.importTogglesStore = stores.importTogglesStore; this.importTogglesStore = stores.importTogglesStore;
this.featureStrategiesStore = stores.featureStrategiesStore; this.featureStrategiesStore = stores.featureStrategiesStore;
@ -135,6 +135,7 @@ export default class ExportImportService {
this.strategyService = strategyService; this.strategyService = strategyService;
this.contextService = contextService; this.contextService = contextService;
this.accessService = accessService; this.accessService = accessService;
this.eventService = eventService;
this.tagTypeService = tagTypeService; this.tagTypeService = tagTypeService;
this.featureTagService = featureTagService; this.featureTagService = featureTagService;
this.importPermissionsService = new ImportPermissionsService( this.importPermissionsService = new ImportPermissionsService(
@ -237,7 +238,7 @@ export default class ExportImportService {
await this.importToggleLevelInfo(cleanedDto, user); await this.importToggleLevelInfo(cleanedDto, user);
await this.importDefault(cleanedDto, user); await this.importDefault(cleanedDto, user);
await this.eventStore.store({ await this.eventService.storeEvent({
project: cleanedDto.project, project: cleanedDto.project,
environment: cleanedDto.environment, environment: cleanedDto.environment,
type: FEATURES_IMPORTED, type: FEATURES_IMPORTED,
@ -726,7 +727,7 @@ export default class ExportImportService {
}), }),
tagTypes: filteredTagTypes, tagTypes: filteredTagTypes,
}; };
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURES_EXPORTED, type: FEATURES_EXPORTED,
createdBy: userName, createdBy: userName,
data: result, data: result,

View File

@ -1,5 +1,6 @@
import { import {
AccessService, AccessService,
EventService,
FeatureToggleService, FeatureToggleService,
GroupService, GroupService,
} from '../../services'; } from '../../services';
@ -7,7 +8,6 @@ import FeatureStrategiesStore from '../../db/feature-strategy-store';
import FeatureToggleStore from '../../db/feature-toggle-store'; import FeatureToggleStore from '../../db/feature-toggle-store';
import FeatureToggleClientStore from '../../db/feature-toggle-client-store'; import FeatureToggleClientStore from '../../db/feature-toggle-client-store';
import ProjectStore from '../../db/project-store'; import ProjectStore from '../../db/project-store';
import FeatureTagStore from '../../db/feature-tag-store';
import { FeatureEnvironmentStore } from '../../db/feature-environment-store'; import { FeatureEnvironmentStore } from '../../db/feature-environment-store';
import ContextFieldStore from '../../db/context-field-store'; import ContextFieldStore from '../../db/context-field-store';
import GroupStore from '../../db/group-store'; import GroupStore from '../../db/group-store';
@ -22,7 +22,6 @@ import FakeFeatureStrategiesStore from '../../../test/fixtures/fake-feature-stra
import FakeFeatureToggleStore from '../../../test/fixtures/fake-feature-toggle-store'; import FakeFeatureToggleStore from '../../../test/fixtures/fake-feature-toggle-store';
import FakeFeatureToggleClientStore from '../../../test/fixtures/fake-feature-toggle-client-store'; import FakeFeatureToggleClientStore from '../../../test/fixtures/fake-feature-toggle-client-store';
import FakeProjectStore from '../../../test/fixtures/fake-project-store'; import FakeProjectStore from '../../../test/fixtures/fake-project-store';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store'; import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store';
import FakeContextFieldStore from '../../../test/fixtures/fake-context-field-store'; import FakeContextFieldStore from '../../../test/fixtures/fake-context-field-store';
import FakeGroupStore from '../../../test/fixtures/fake-group-store'; import FakeGroupStore from '../../../test/fixtures/fake-group-store';
@ -47,6 +46,8 @@ import {
} from '../private-project/createPrivateProjectChecker'; } from '../private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model'; import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
import FeatureTagStore from '../../db/feature-tag-store';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
export const createFeatureToggleService = ( export const createFeatureToggleService = (
db: Db, db: Db,
@ -72,7 +73,6 @@ export const createFeatureToggleService = (
getLogger, getLogger,
flagResolver, flagResolver,
); );
const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
const featureEnvironmentStore = new FeatureEnvironmentStore( const featureEnvironmentStore = new FeatureEnvironmentStore(
db, db,
eventBus, eventBus,
@ -87,13 +87,19 @@ export const createFeatureToggleService = (
const strategyStore = new StrategyStore(db, getLogger); const strategyStore = new StrategyStore(db, getLogger);
const accountStore = new AccountStore(db, getLogger); const accountStore = new AccountStore(db, getLogger);
const accessStore = new AccessStore(db, eventBus, getLogger); const accessStore = new AccessStore(db, eventBus, getLogger);
const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
const roleStore = new RoleStore(db, eventBus, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger);
const environmentStore = new EnvironmentStore(db, eventBus, getLogger); const environmentStore = new EnvironmentStore(db, eventBus, getLogger);
const eventStore = new EventStore(db, getLogger); const eventStore = new EventStore(db, getLogger);
const groupService = new GroupService( const eventService = new EventService(
{ groupStore, eventStore, accountStore }, { eventStore, featureTagStore },
{ getLogger }, { getLogger },
); );
const groupService = new GroupService(
{ groupStore, accountStore },
{ getLogger },
eventService,
);
const accessService = new AccessService( const accessService = new AccessService(
{ accessStore, accountStore, roleStore, environmentStore, groupStore }, { accessStore, accountStore, roleStore, environmentStore, groupStore },
{ getLogger, flagResolver }, { getLogger, flagResolver },
@ -115,7 +121,6 @@ export const createFeatureToggleService = (
featureToggleStore, featureToggleStore,
featureToggleClientStore, featureToggleClientStore,
projectStore, projectStore,
eventStore,
featureTagStore, featureTagStore,
featureEnvironmentStore, featureEnvironmentStore,
contextFieldStore, contextFieldStore,
@ -124,6 +129,7 @@ export const createFeatureToggleService = (
{ getLogger, flagResolver }, { getLogger, flagResolver },
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
@ -141,18 +147,23 @@ export const createFakeFeatureToggleService = (
const featureToggleStore = new FakeFeatureToggleStore(); const featureToggleStore = new FakeFeatureToggleStore();
const featureToggleClientStore = new FakeFeatureToggleClientStore(); const featureToggleClientStore = new FakeFeatureToggleClientStore();
const projectStore = new FakeProjectStore(); const projectStore = new FakeProjectStore();
const featureTagStore = new FakeFeatureTagStore();
const featureEnvironmentStore = new FakeFeatureEnvironmentStore(); const featureEnvironmentStore = new FakeFeatureEnvironmentStore();
const contextFieldStore = new FakeContextFieldStore(); const contextFieldStore = new FakeContextFieldStore();
const groupStore = new FakeGroupStore(); const groupStore = new FakeGroupStore();
const accountStore = new FakeAccountStore(); const accountStore = new FakeAccountStore();
const accessStore = new FakeAccessStore(); const accessStore = new FakeAccessStore();
const featureTagStore = new FakeFeatureTagStore();
const roleStore = new FakeRoleStore(); const roleStore = new FakeRoleStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const groupService = new GroupService( const eventService = new EventService(
{ groupStore, eventStore, accountStore }, { eventStore, featureTagStore },
{ getLogger }, { getLogger },
); );
const groupService = new GroupService(
{ groupStore, accountStore },
{ getLogger },
eventService,
);
const accessService = new AccessService( const accessService = new AccessService(
{ accessStore, accountStore, roleStore, environmentStore, groupStore }, { accessStore, accountStore, roleStore, environmentStore, groupStore },
{ getLogger, flagResolver }, { getLogger, flagResolver },
@ -168,7 +179,6 @@ export const createFakeFeatureToggleService = (
featureToggleStore, featureToggleStore,
featureToggleClientStore, featureToggleClientStore,
projectStore, projectStore,
eventStore,
featureTagStore, featureTagStore,
featureEnvironmentStore, featureEnvironmentStore,
contextFieldStore, contextFieldStore,
@ -177,6 +187,7 @@ export const createFakeFeatureToggleService = (
{ getLogger, flagResolver }, { getLogger, flagResolver },
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
fakePrivateProjectChecker, fakePrivateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,

View File

@ -1,21 +1,28 @@
import { IUnleashConfig } from '../../types'; import { IUnleashConfig } from '../../types';
import { GroupService } from '../../services'; import { EventService, GroupService } from '../../services';
import { Db } from '../../db/db'; import { Db } from '../../db/db';
import GroupStore from '../../db/group-store'; import GroupStore from '../../db/group-store';
import { AccountStore } from '../../db/account-store'; import { AccountStore } from '../../db/account-store';
import EventStore from '../../db/event-store'; import EventStore from '../../db/event-store';
import FeatureTagStore from '../../db/feature-tag-store';
export const createGroupService = ( export const createGroupService = (
db: Db, db: Db,
config: IUnleashConfig, config: IUnleashConfig,
): GroupService => { ): GroupService => {
const { getLogger } = config; const { getLogger, eventBus } = config;
const groupStore = new GroupStore(db); const groupStore = new GroupStore(db);
const accountStore = new AccountStore(db, getLogger); const accountStore = new AccountStore(db, getLogger);
const eventStore = new EventStore(db, getLogger); const eventStore = new EventStore(db, getLogger);
const groupService = new GroupService( const featureTagStore = new FeatureTagStore(db, eventBus, getLogger);
{ groupStore, eventStore, accountStore }, const eventService = new EventService(
{ eventStore, featureTagStore },
{ getLogger }, { getLogger },
); );
const groupService = new GroupService(
{ groupStore, accountStore },
{ getLogger },
eventService,
);
return groupService; return groupService;
}; };

View File

@ -5,6 +5,7 @@ import { AccountStore } from '../../db/account-store';
import EnvironmentStore from '../../db/environment-store'; import EnvironmentStore from '../../db/environment-store';
import { import {
AccessService, AccessService,
EventService,
FavoritesService, FavoritesService,
GroupService, GroupService,
ProjectService, ProjectService,
@ -39,6 +40,7 @@ import {
createFakePrivateProjectChecker, createFakePrivateProjectChecker,
createPrivateProjectChecker, createPrivateProjectChecker,
} from '../private-project/createPrivateProjectChecker'; } from '../private-project/createPrivateProjectChecker';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
export const createProjectService = ( export const createProjectService = (
db: Db, db: Db,
@ -75,17 +77,25 @@ export const createProjectService = (
eventBus, eventBus,
getLogger, getLogger,
); );
const eventService = new EventService(
{
eventStore,
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
const favoriteService = new FavoritesService( const favoriteService = new FavoritesService(
{ {
favoriteFeaturesStore, favoriteFeaturesStore,
favoriteProjectsStore, favoriteProjectsStore,
eventStore,
}, },
config, config,
eventService,
); );
const groupService = new GroupService( const groupService = new GroupService(
{ groupStore, eventStore, accountStore }, { groupStore, accountStore },
{ getLogger }, { getLogger },
eventService,
); );
const privateProjectChecker = createPrivateProjectChecker(db, config); const privateProjectChecker = createPrivateProjectChecker(db, config);
@ -106,6 +116,7 @@ export const createProjectService = (
featureToggleService, featureToggleService,
groupService, groupService,
favoriteService, favoriteService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
}; };
@ -127,17 +138,25 @@ export const createFakeProjectService = (
const featureToggleService = createFakeFeatureToggleService(config); const featureToggleService = createFakeFeatureToggleService(config);
const favoriteFeaturesStore = new FakeFavoriteFeaturesStore(); const favoriteFeaturesStore = new FakeFavoriteFeaturesStore();
const favoriteProjectsStore = new FakeFavoriteProjectsStore(); const favoriteProjectsStore = new FakeFavoriteProjectsStore();
const eventService = new EventService(
{
eventStore,
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
const favoriteService = new FavoritesService( const favoriteService = new FavoritesService(
{ {
favoriteFeaturesStore, favoriteFeaturesStore,
favoriteProjectsStore, favoriteProjectsStore,
eventStore,
}, },
config, config,
eventService,
); );
const groupService = new GroupService( const groupService = new GroupService(
{ groupStore, eventStore, accountStore }, { groupStore, accountStore },
{ getLogger }, { getLogger },
eventService,
); );
const privateProjectChecker = createFakePrivateProjectChecker(); const privateProjectChecker = createFakePrivateProjectChecker();
@ -158,6 +177,7 @@ export const createFakeProjectService = (
featureToggleService, featureToggleService,
groupService, groupService,
favoriteService, favoriteService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
}; };

View File

@ -1,6 +1,6 @@
import { Db, IUnleashConfig } from 'lib/server-impl'; import { Db, IUnleashConfig } from 'lib/server-impl';
import EventStore from '../../db/event-store'; import EventStore from '../../db/event-store';
import { SegmentService } from '../../services'; import { EventService, SegmentService } from '../../services';
import FakeEventStore from '../../../test/fixtures/fake-event-store'; import FakeEventStore from '../../../test/fixtures/fake-event-store';
import { ISegmentService } from '../../segments/segment-service-interface'; import { ISegmentService } from '../../segments/segment-service-interface';
import FeatureStrategiesStore from '../../db/feature-strategy-store'; import FeatureStrategiesStore from '../../db/feature-strategy-store';
@ -15,6 +15,8 @@ import {
createFakePrivateProjectChecker, createFakePrivateProjectChecker,
createPrivateProjectChecker, createPrivateProjectChecker,
} from '../private-project/createPrivateProjectChecker'; } from '../private-project/createPrivateProjectChecker';
import FeatureTagStore from '../../db/feature-tag-store';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
export const createSegmentService = ( export const createSegmentService = (
db: Db, db: Db,
@ -40,10 +42,19 @@ export const createSegmentService = (
); );
const privateProjectChecker = createPrivateProjectChecker(db, config); const privateProjectChecker = createPrivateProjectChecker(db, config);
const eventService = new EventService(
{
eventStore,
featureTagStore: new FeatureTagStore(db, eventBus, getLogger),
},
config,
);
return new SegmentService( return new SegmentService(
{ segmentStore, featureStrategiesStore, eventStore }, { segmentStore, featureStrategiesStore },
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
}; };
@ -58,10 +69,19 @@ export const createFakeSegmentService = (
const privateProjectChecker = createFakePrivateProjectChecker(); const privateProjectChecker = createFakePrivateProjectChecker();
const eventService = new EventService(
{
eventStore,
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
return new SegmentService( return new SegmentService(
{ segmentStore, featureStrategiesStore, eventStore }, { segmentStore, featureStrategiesStore },
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
}; };

View File

@ -4,10 +4,11 @@ import { createTestConfig } from '../../test/config/test-config';
import FakeEventStore from '../../test/fixtures/fake-event-store'; import FakeEventStore from '../../test/fixtures/fake-event-store';
import { randomId } from '../util/random-id'; import { randomId } from '../util/random-id';
import FakeProjectStore from '../../test/fixtures/fake-project-store'; import FakeProjectStore from '../../test/fixtures/fake-project-store';
import { ProxyService, SettingService } from '../../lib/services'; import { EventService, ProxyService, SettingService } from '../../lib/services';
import { ISettingStore } from '../../lib/types'; import { ISettingStore } from '../../lib/types';
import { frontendSettingsKey } from '../../lib/types/settings/frontend-settings'; import { frontendSettingsKey } from '../../lib/types/settings/frontend-settings';
import { minutesToMilliseconds } from 'date-fns'; import { minutesToMilliseconds } from 'date-fns';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
const createSettingService = ( const createSettingService = (
frontendApiOrigins: string[], frontendApiOrigins: string[],
@ -17,11 +18,14 @@ const createSettingService = (
const stores = { const stores = {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
projectStore: new FakeProjectStore(), projectStore: new FakeProjectStore(),
}; };
const eventService = new EventService(stores, config);
const services = { const services = {
settingService: new SettingService(stores, config), settingService: new SettingService(stores, config, eventService),
}; };
return { return {
@ -135,8 +139,8 @@ test('corsOriginMiddleware with caching enabled', async () => {
/* /*
This is needed because it is not enough to fake time to test the This is needed because it is not enough to fake time to test the
updated cache, we also need to make sure that all promises are updated cache, we also need to make sure that all promises are
executed and completed, in the right order. executed and completed, in the right order.
*/ */
await new Promise<void>((resolve) => await new Promise<void>((resolve) =>
process.nextTick(async () => { process.nextTick(async () => {

View File

@ -22,7 +22,11 @@ async function getSetup(anonymise: boolean = false) {
const services = createServices(stores, config); const services = createServices(stores, config);
const app = await getApp(config, stores, services); const app = await getApp(config, stores, services);
return { base, eventStore: stores.eventStore, request: supertest(app) }; return {
base,
eventService: services.eventService,
request: supertest(app),
};
} }
test('should get empty events list via admin', async () => { test('should get empty events list via admin', async () => {
@ -38,14 +42,13 @@ test('should get empty events list via admin', async () => {
}); });
test('should get events list via admin', async () => { test('should get events list via admin', async () => {
const { request, base, eventStore } = await getSetup(); const { request, base, eventService } = await getSetup();
eventStore.store( eventService.storeEvent(
new FeatureCreatedEvent({ new FeatureCreatedEvent({
createdBy: 'some@email.com', createdBy: 'some@email.com',
data: { name: 'test', project: 'default' }, data: { name: 'test', project: 'default' },
featureName: 'test', featureName: 'test',
project: 'default', project: 'default',
tags: [],
}), }),
); );
const { body } = await request const { body } = await request
@ -58,14 +61,13 @@ test('should get events list via admin', async () => {
}); });
test('should anonymise events list via admin', async () => { test('should anonymise events list via admin', async () => {
const { request, base, eventStore } = await getSetup(true); const { request, base, eventService } = await getSetup(true);
eventStore.store( eventService.storeEvent(
new FeatureCreatedEvent({ new FeatureCreatedEvent({
createdBy: 'some@email.com', createdBy: 'some@email.com',
data: { name: 'test', project: 'default' }, data: { name: 'test', project: 'default' },
featureName: 'test', featureName: 'test',
project: 'default', project: 'default',
tags: [],
}), }),
); );
const { body } = await request const { body } = await request
@ -81,15 +83,15 @@ test('should also anonymise email fields in data and preData properties', async
const email1 = 'test1@email.com'; const email1 = 'test1@email.com';
const email2 = 'test2@email.com'; const email2 = 'test2@email.com';
const { request, base, eventStore } = await getSetup(true); const { request, base, eventService } = await getSetup(true);
eventStore.store( eventService.storeEvent(
new ProjectUserAddedEvent({ new ProjectUserAddedEvent({
createdBy: 'some@email.com', createdBy: 'some@email.com',
data: { name: 'test', project: 'default', email: email1 }, data: { name: 'test', project: 'default', email: email1 },
project: 'default', project: 'default',
}), }),
); );
eventStore.store( eventService.storeEvent(
new ProjectUserRemovedEvent({ new ProjectUserRemovedEvent({
createdBy: 'some@email.com', createdBy: 'some@email.com',
preData: { name: 'test', project: 'default', email: email2 }, preData: { name: 'test', project: 'default', email: email2 },
@ -109,8 +111,8 @@ test('should also anonymise email fields in data and preData properties', async
test('should anonymise any PII fields, no matter the depth', async () => { test('should anonymise any PII fields, no matter the depth', async () => {
const testUsername = 'test-username'; const testUsername = 'test-username';
const { request, base, eventStore } = await getSetup(true); const { request, base, eventService } = await getSetup(true);
eventStore.store( eventService.storeEvent(
new ProjectAccessAddedEvent({ new ProjectAccessAddedEvent({
createdBy: 'some@email.com', createdBy: 'some@email.com',
data: { data: {

View File

@ -13,6 +13,8 @@ import { GroupService } from '../services/group-service';
import FakeEventStore from '../../test/fixtures/fake-event-store'; import FakeEventStore from '../../test/fixtures/fake-event-store';
import { IRole } from 'lib/types/stores/access-store'; import { IRole } from 'lib/types/stores/access-store';
import { IGroup } from 'lib/types'; import { IGroup } from 'lib/types';
import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
function getSetup(customRootRolesKillSwitch: boolean = true) { function getSetup(customRootRolesKillSwitch: boolean = true) {
const config = createTestConfig({ const config = createTestConfig({
@ -204,9 +206,14 @@ test('throws error when trying to delete a project role in use by group', async
accessStore.get = async (): Promise<IRole> => { accessStore.get = async (): Promise<IRole> => {
return { id: 1, type: 'custom', name: 'project role' }; return { id: 1, type: 'custom', name: 'project role' };
}; };
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const groupService = new GroupService( const groupService = new GroupService(
{ groupStore, eventStore, accountStore }, { groupStore, accountStore },
{ getLogger }, { getLogger },
eventService,
); );
const accessService = new AccessService( const accessService = new AccessService(

View File

@ -23,7 +23,7 @@ export class AccountService {
private lastSeenSecrets: Set<string> = new Set<string>(); private lastSeenSecrets: Set<string> = new Set<string>();
constructor( constructor(
stores: Pick<IUnleashStores, 'accountStore' | 'eventStore'>, stores: Pick<IUnleashStores, 'accountStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
services: { services: {
accessService: AccessService; accessService: AccessService;

View File

@ -14,6 +14,7 @@ import AddonService from './addon-service';
import { IAddonDto } from '../types/stores/addon-store'; import { IAddonDto } from '../types/stores/addon-store';
import SimpleAddon from './addon-service-test-simple-addon'; import SimpleAddon from './addon-service-test-simple-addon';
import { IAddonProviders } from '../addons'; import { IAddonProviders } from '../addons';
import EventService from './event-service';
const MASKED_VALUE = '*****'; const MASKED_VALUE = '*****';
@ -21,7 +22,12 @@ let addonProvider: IAddonProviders;
function getSetup() { function getSetup() {
const stores = createStores(); const stores = createStores();
const tagTypeService = new TagTypeService(stores, { getLogger }); const eventService = new EventService(stores, { getLogger });
const tagTypeService = new TagTypeService(
stores,
{ getLogger },
eventService,
);
addonProvider = { simple: new SimpleAddon() }; addonProvider = { simple: new SimpleAddon() };
return { return {
@ -33,8 +39,10 @@ function getSetup() {
server: { unleashUrl: 'http://test' }, server: { unleashUrl: 'http://test' },
}, },
tagTypeService, tagTypeService,
eventService,
addonProvider, addonProvider,
), ),
eventService,
stores, stores,
tagTypeService, tagTypeService,
}; };
@ -77,7 +85,7 @@ test('should not allow addon-config for unknown provider', async () => {
}); });
test('should trigger simple-addon eventHandler', async () => { test('should trigger simple-addon eventHandler', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const config = { const config = {
provider: 'simple', provider: 'simple',
@ -93,7 +101,7 @@ test('should trigger simple-addon eventHandler', async () => {
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
// Feature toggle was created // Feature toggle was created
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
data: { data: {
@ -113,7 +121,7 @@ test('should trigger simple-addon eventHandler', async () => {
}); });
test('should not trigger event handler if project of event is different from addon', async () => { test('should not trigger event handler if project of event is different from addon', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const config = { const config = {
provider: 'simple', provider: 'simple',
enabled: true, enabled: true,
@ -126,7 +134,7 @@ test('should not trigger event handler if project of event is different from add
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: 'someotherproject', project: 'someotherproject',
@ -144,7 +152,7 @@ test('should not trigger event handler if project of event is different from add
}); });
test('should trigger event handler if project for event is one of the desired projects for addon', async () => { test('should trigger event handler if project for event is one of the desired projects for addon', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const desiredProject = 'desired'; const desiredProject = 'desired';
const otherProject = 'other'; const otherProject = 'other';
const config = { const config = {
@ -159,7 +167,7 @@ test('should trigger event handler if project for event is one of the desired pr
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProject, project: desiredProject,
@ -169,7 +177,7 @@ test('should trigger event handler if project for event is one of the desired pr
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: otherProject, project: otherProject,
@ -189,7 +197,7 @@ test('should trigger event handler if project for event is one of the desired pr
}); });
test('should trigger events for multiple projects if addon is setup to filter multiple projects', async () => { test('should trigger events for multiple projects if addon is setup to filter multiple projects', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const desiredProjects = ['desired', 'desired2']; const desiredProjects = ['desired', 'desired2'];
const otherProject = 'other'; const otherProject = 'other';
const config = { const config = {
@ -204,7 +212,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[0], project: desiredProjects[0],
@ -214,7 +222,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: otherProject, project: otherProject,
@ -224,7 +232,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[1], project: desiredProjects[1],
@ -246,7 +254,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
}); });
test('should filter events on environment if addon is setup to filter for it', async () => { test('should filter events on environment if addon is setup to filter for it', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const desiredEnvironment = 'desired'; const desiredEnvironment = 'desired';
const otherEnvironment = 'other'; const otherEnvironment = 'other';
const config = { const config = {
@ -262,7 +270,7 @@ test('should filter events on environment if addon is setup to filter for it', a
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredEnvironment, project: desiredEnvironment,
@ -273,7 +281,7 @@ test('should filter events on environment if addon is setup to filter for it', a
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
environment: otherEnvironment, environment: otherEnvironment,
@ -293,7 +301,7 @@ test('should filter events on environment if addon is setup to filter for it', a
}); });
test('should not filter out global events (no specific environment) even if addon is setup to filter for environments', async () => { test('should not filter out global events (no specific environment) even if addon is setup to filter for environments', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const filteredEnvironment = 'filtered'; const filteredEnvironment = 'filtered';
const config = { const config = {
provider: 'simple', provider: 'simple',
@ -319,7 +327,7 @@ test('should not filter out global events (no specific environment) even if addo
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store(globalEventWithNoEnvironment); await eventService.storeEvent(globalEventWithNoEnvironment);
const simpleProvider = addonService.addonProviders.simple; const simpleProvider = addonService.addonProviders.simple;
// @ts-expect-error // @ts-expect-error
const events = simpleProvider.getEvents(); const events = simpleProvider.getEvents();
@ -330,7 +338,7 @@ test('should not filter out global events (no specific environment) even if addo
}); });
test('should not filter out global events (no specific project) even if addon is setup to filter for projects', async () => { test('should not filter out global events (no specific project) even if addon is setup to filter for projects', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const filteredProject = 'filtered'; const filteredProject = 'filtered';
const config = { const config = {
provider: 'simple', provider: 'simple',
@ -355,7 +363,7 @@ test('should not filter out global events (no specific project) even if addon is
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store(globalEventWithNoProject); await eventService.storeEvent(globalEventWithNoProject);
const simpleProvider = addonService.addonProviders.simple; const simpleProvider = addonService.addonProviders.simple;
// @ts-expect-error // @ts-expect-error
const events = simpleProvider.getEvents(); const events = simpleProvider.getEvents();
@ -366,7 +374,7 @@ test('should not filter out global events (no specific project) even if addon is
}); });
test('should support wildcard option for filtering addons', async () => { test('should support wildcard option for filtering addons', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const desiredProjects = ['desired', 'desired2']; const desiredProjects = ['desired', 'desired2'];
const otherProject = 'other'; const otherProject = 'other';
const config = { const config = {
@ -381,7 +389,7 @@ test('should support wildcard option for filtering addons', async () => {
}; };
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[0], project: desiredProjects[0],
@ -391,7 +399,7 @@ test('should support wildcard option for filtering addons', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: otherProject, project: otherProject,
@ -401,7 +409,7 @@ test('should support wildcard option for filtering addons', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[1], project: desiredProjects[1],
@ -425,7 +433,7 @@ test('should support wildcard option for filtering addons', async () => {
}); });
test('Should support filtering by both project and environment', async () => { test('Should support filtering by both project and environment', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const desiredProjects = ['desired1', 'desired2', 'desired3']; const desiredProjects = ['desired1', 'desired2', 'desired3'];
const desiredEnvironments = ['env1', 'env2', 'env3']; const desiredEnvironments = ['env1', 'env2', 'env3'];
const config = { const config = {
@ -445,7 +453,7 @@ test('Should support filtering by both project and environment', async () => {
'desired-toggle3', 'desired-toggle3',
]; ];
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[0], project: desiredProjects[0],
@ -456,7 +464,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[0], project: desiredProjects[0],
@ -467,7 +475,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[2], project: desiredProjects[2],
@ -478,7 +486,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: desiredProjects[2], project: desiredProjects[2],
@ -489,7 +497,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }], strategies: [{ name: 'default' }],
}, },
}); });
await stores.eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
createdBy: 'some@user.com', createdBy: 'some@user.com',
project: 'wrongproject', project: 'wrongproject',
@ -556,7 +564,7 @@ test('should create tag type for simple-addon', async () => {
}); });
test('should store ADDON_CONFIG_CREATE event', async () => { test('should store ADDON_CONFIG_CREATE event', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const config = { const config = {
provider: 'simple', provider: 'simple',
@ -571,7 +579,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => {
await addonService.createAddon(config, 'me@mail.com'); await addonService.createAddon(config, 'me@mail.com');
const events = await stores.eventStore.getEvents(); const { events } = await eventService.getEvents();
expect(events.length).toBe(2); // Also tag-types where created expect(events.length).toBe(2); // Also tag-types where created
expect(events[1].type).toBe(ADDON_CONFIG_CREATED); expect(events[1].type).toBe(ADDON_CONFIG_CREATED);
@ -579,7 +587,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => {
}); });
test('should store ADDON_CONFIG_UPDATE event', async () => { test('should store ADDON_CONFIG_UPDATE event', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const config: IAddonDto = { const config: IAddonDto = {
description: '', description: '',
@ -597,7 +605,7 @@ test('should store ADDON_CONFIG_UPDATE event', async () => {
const updated = { ...addonConfig, description: 'test' }; const updated = { ...addonConfig, description: 'test' };
await addonService.updateAddon(addonConfig.id, updated, 'me@mail.com'); await addonService.updateAddon(addonConfig.id, updated, 'me@mail.com');
const events = await stores.eventStore.getEvents(); const { events } = await eventService.getEvents();
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].type).toBe(ADDON_CONFIG_UPDATED); expect(events[2].type).toBe(ADDON_CONFIG_UPDATED);
@ -605,7 +613,7 @@ test('should store ADDON_CONFIG_UPDATE event', async () => {
}); });
test('should store ADDON_CONFIG_REMOVE event', async () => { test('should store ADDON_CONFIG_REMOVE event', async () => {
const { addonService, stores } = getSetup(); const { addonService, eventService } = getSetup();
const config: IAddonDto = { const config: IAddonDto = {
provider: 'simple', provider: 'simple',
@ -622,7 +630,7 @@ test('should store ADDON_CONFIG_REMOVE event', async () => {
await addonService.removeAddon(addonConfig.id, 'me@mail.com'); await addonService.removeAddon(addonConfig.id, 'me@mail.com');
const events = await stores.eventStore.getEvents(); const { events } = await eventService.getEvents();
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[2].type).toBe(ADDON_CONFIG_DELETED); expect(events[2].type).toBe(ADDON_CONFIG_DELETED);

View File

@ -4,7 +4,6 @@ import { getAddons, IAddonProviders } from '../addons';
import * as events from '../types/events'; import * as events from '../types/events';
import { addonSchema } from './addon-schema'; import { addonSchema } from './addon-schema';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
import { IEventStore } from '../types/stores/event-store';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { Logger } from '../logger'; import { Logger } from '../logger';
import TagTypeService from './tag-type-service'; import TagTypeService from './tag-type-service';
@ -12,6 +11,7 @@ import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
import { IUnleashStores, IUnleashConfig } from '../types'; import { IUnleashStores, IUnleashConfig } from '../types';
import { IAddonDefinition } from '../types/model'; import { IAddonDefinition } from '../types/model';
import { minutesToMilliseconds } from 'date-fns'; import { minutesToMilliseconds } from 'date-fns';
import EventService from './event-service';
const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]); const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]);
@ -23,8 +23,6 @@ interface ISensitiveParams {
[key: string]: string[]; [key: string]: string[];
} }
export default class AddonService { export default class AddonService {
eventStore: IEventStore;
addonStore: IAddonStore; addonStore: IAddonStore;
featureToggleStore: IFeatureToggleStore; featureToggleStore: IFeatureToggleStore;
@ -33,6 +31,8 @@ export default class AddonService {
tagTypeService: TagTypeService; tagTypeService: TagTypeService;
eventService: EventService;
addonProviders: IAddonProviders; addonProviders: IAddonProviders;
sensitiveParams: ISensitiveParams; sensitiveParams: ISensitiveParams;
@ -43,25 +43,22 @@ export default class AddonService {
constructor( constructor(
{ {
addonStore, addonStore,
eventStore,
featureToggleStore, featureToggleStore,
}: Pick< }: Pick<IUnleashStores, 'addonStore' | 'featureToggleStore'>,
IUnleashStores,
'addonStore' | 'eventStore' | 'featureToggleStore'
>,
{ {
getLogger, getLogger,
server, server,
flagResolver, flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'server' | 'flagResolver'>, }: Pick<IUnleashConfig, 'getLogger' | 'server' | 'flagResolver'>,
tagTypeService: TagTypeService, tagTypeService: TagTypeService,
eventService: EventService,
addons?: IAddonProviders, addons?: IAddonProviders,
) { ) {
this.eventStore = eventStore;
this.addonStore = addonStore; this.addonStore = addonStore;
this.featureToggleStore = featureToggleStore; this.featureToggleStore = featureToggleStore;
this.logger = getLogger('services/addon-service.js'); this.logger = getLogger('services/addon-service.js');
this.tagTypeService = tagTypeService; this.tagTypeService = tagTypeService;
this.eventService = eventService;
this.addonProviders = this.addonProviders =
addons || addons ||
@ -102,7 +99,7 @@ export default class AddonService {
registerEventHandler(): void { registerEventHandler(): void {
SUPPORTED_EVENTS.forEach((eventName) => SUPPORTED_EVENTS.forEach((eventName) =>
this.eventStore.on(eventName, this.handleEvent(eventName)), this.eventService.onEvent(eventName, this.handleEvent(eventName)),
); );
} }
@ -205,7 +202,7 @@ export default class AddonService {
`User ${userName} created addon ${addonConfig.provider}`, `User ${userName} created addon ${addonConfig.provider}`,
); );
await this.eventStore.store({ await this.eventService.storeEvent({
type: events.ADDON_CONFIG_CREATED, type: events.ADDON_CONFIG_CREATED,
createdBy: userName, createdBy: userName,
data: { provider: addonConfig.provider }, data: { provider: addonConfig.provider },
@ -238,7 +235,7 @@ export default class AddonService {
); );
} }
const result = await this.addonStore.update(id, addonConfig); const result = await this.addonStore.update(id, addonConfig);
await this.eventStore.store({ await this.eventService.storeEvent({
type: events.ADDON_CONFIG_UPDATED, type: events.ADDON_CONFIG_UPDATED,
createdBy: userName, createdBy: userName,
data: { id, provider: addonConfig.provider }, data: { id, provider: addonConfig.provider },
@ -249,7 +246,7 @@ export default class AddonService {
async removeAddon(id: number, userName: string): Promise<void> { async removeAddon(id: number, userName: string): Promise<void> {
await this.addonStore.delete(id); await this.addonStore.delete(id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: events.ADDON_CONFIG_DELETED, type: events.ADDON_CONFIG_DELETED,
createdBy: userName, createdBy: userName,
data: { id }, data: { id },

View File

@ -11,6 +11,8 @@ import {
API_TOKEN_UPDATED, API_TOKEN_UPDATED,
} from '../types'; } from '../types';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
test('Should init api token', async () => { test('Should init api token', async () => {
const token = { const token = {
@ -28,16 +30,24 @@ test('Should init api token', async () => {
}); });
const apiTokenStore = new FakeApiTokenStore(); const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const eventStore = new FakeEventStore();
const insertCalled = new Promise((resolve) => { const insertCalled = new Promise((resolve) => {
apiTokenStore.on('insert', resolve); apiTokenStore.on('insert', resolve);
}); });
new ApiTokenService( const eventService = new EventService(
{ apiTokenStore, environmentStore, eventStore }, {
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
},
config, config,
); );
new ApiTokenService(
{ apiTokenStore, environmentStore },
config,
eventService,
);
await insertCalled; await insertCalled;
const tokens = await apiTokenStore.getAll(); const tokens = await apiTokenStore.getAll();
@ -58,7 +68,14 @@ test("Shouldn't return frontend token when secret is undefined", async () => {
const config: IUnleashConfig = createTestConfig({}); const config: IUnleashConfig = createTestConfig({});
const apiTokenStore = new FakeApiTokenStore(); const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const eventStore = new FakeEventStore();
const eventService = new EventService(
{
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
await environmentStore.create({ await environmentStore.create({
name: 'default', name: 'default',
@ -69,8 +86,9 @@ test("Shouldn't return frontend token when secret is undefined", async () => {
}); });
const apiTokenService = new ApiTokenService( const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore, eventStore }, { apiTokenStore, environmentStore },
config, config,
eventService,
); );
await apiTokenService.createApiTokenWithProjects(token); await apiTokenService.createApiTokenWithProjects(token);
@ -93,7 +111,14 @@ test('Api token operations should all have events attached', async () => {
const config: IUnleashConfig = createTestConfig({}); const config: IUnleashConfig = createTestConfig({});
const apiTokenStore = new FakeApiTokenStore(); const apiTokenStore = new FakeApiTokenStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const eventStore = new FakeEventStore();
const eventService = new EventService(
{
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
await environmentStore.create({ await environmentStore.create({
name: 'default', name: 'default',
@ -104,14 +129,15 @@ test('Api token operations should all have events attached', async () => {
}); });
const apiTokenService = new ApiTokenService( const apiTokenService = new ApiTokenService(
{ apiTokenStore, environmentStore, eventStore }, { apiTokenStore, environmentStore },
config, config,
eventService,
); );
let saved = await apiTokenService.createApiTokenWithProjects(token); let saved = await apiTokenService.createApiTokenWithProjects(token);
let newExpiry = addDays(new Date(), 30); let newExpiry = addDays(new Date(), 30);
await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test'); await apiTokenService.updateExpiry(saved.secret, newExpiry, 'test');
await apiTokenService.delete(saved.secret, 'test'); await apiTokenService.delete(saved.secret, 'test');
const events = await eventStore.getEvents(); const { events } = await eventService.getEvents();
const createdApiTokenEvents = events.filter( const createdApiTokenEvents = events.filter(
(e) => e.type === API_TOKEN_CREATED, (e) => e.type === API_TOKEN_CREATED,
); );

View File

@ -1,7 +1,7 @@
import crypto from 'crypto'; import crypto from 'crypto';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { ADMIN, CLIENT, FRONTEND } from '../types/permissions'; import { ADMIN, CLIENT, FRONTEND } from '../types/permissions';
import { IEventStore, IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import ApiUser from '../types/api-user'; import ApiUser from '../types/api-user';
import { import {
@ -25,6 +25,7 @@ import {
ApiTokenUpdatedEvent, ApiTokenUpdatedEvent,
} from '../types'; } from '../types';
import { omitKeys } from '../util'; import { omitKeys } from '../util';
import EventService from './event-service';
const resolveTokenPermissions = (tokenType: string) => { const resolveTokenPermissions = (tokenType: string) => {
if (tokenType === ApiTokenType.ADMIN) { if (tokenType === ApiTokenType.ADMIN) {
@ -51,7 +52,7 @@ export class ApiTokenService {
private activeTokens: IApiToken[] = []; private activeTokens: IApiToken[] = [];
private eventStore: IEventStore; private eventService: EventService;
private lastSeenSecrets: Set<string> = new Set<string>(); private lastSeenSecrets: Set<string> = new Set<string>();
@ -59,15 +60,12 @@ export class ApiTokenService {
{ {
apiTokenStore, apiTokenStore,
environmentStore, environmentStore,
eventStore, }: Pick<IUnleashStores, 'apiTokenStore' | 'environmentStore'>,
}: Pick<
IUnleashStores,
'apiTokenStore' | 'environmentStore' | 'eventStore'
>,
config: Pick<IUnleashConfig, 'getLogger' | 'authentication'>, config: Pick<IUnleashConfig, 'getLogger' | 'authentication'>,
eventService: EventService,
) { ) {
this.store = apiTokenStore; this.store = apiTokenStore;
this.eventStore = eventStore; this.eventService = eventService;
this.environmentStore = environmentStore; this.environmentStore = environmentStore;
this.logger = config.getLogger('/services/api-token-service.ts'); this.logger = config.getLogger('/services/api-token-service.ts');
this.fetchActiveTokens(); this.fetchActiveTokens();
@ -167,7 +165,7 @@ export class ApiTokenService {
): Promise<IApiToken> { ): Promise<IApiToken> {
const previous = await this.store.get(secret); const previous = await this.store.get(secret);
const token = await this.store.setExpiry(secret, expiresAt); const token = await this.store.setExpiry(secret, expiresAt);
await this.eventStore.store( await this.eventService.storeEvent(
new ApiTokenUpdatedEvent({ new ApiTokenUpdatedEvent({
createdBy: updatedBy, createdBy: updatedBy,
previousToken: omitKeys(previous, 'secret'), previousToken: omitKeys(previous, 'secret'),
@ -181,7 +179,7 @@ export class ApiTokenService {
if (await this.store.exists(secret)) { if (await this.store.exists(secret)) {
const token = await this.store.get(secret); const token = await this.store.get(secret);
await this.store.delete(secret); await this.store.delete(secret);
await this.eventStore.store( await this.eventService.storeEvent(
new ApiTokenDeletedEvent({ new ApiTokenDeletedEvent({
createdBy: deletedBy, createdBy: deletedBy,
apiToken: omitKeys(token, 'secret'), apiToken: omitKeys(token, 'secret'),
@ -233,7 +231,7 @@ export class ApiTokenService {
try { try {
const token = await this.store.insert(newApiToken); const token = await this.store.insert(newApiToken);
this.activeTokens.push(token); this.activeTokens.push(token);
await this.eventStore.store( await this.eventService.storeEvent(
new ApiTokenCreatedEvent({ new ApiTokenCreatedEvent({
createdBy, createdBy,
apiToken: omitKeys(token, 'secret'), apiToken: omitKeys(token, 'secret'),

View File

@ -4,13 +4,13 @@ import {
IContextFieldDto, IContextFieldDto,
IContextFieldStore, IContextFieldStore,
} from '../types/stores/context-field-store'; } from '../types/stores/context-field-store';
import { IEventStore } from '../types/stores/event-store';
import { IProjectStore } from '../types/stores/project-store'; import { IProjectStore } from '../types/stores/project-store';
import { IFeatureStrategiesStore, IUnleashStores } from '../types/stores'; import { IFeatureStrategiesStore, IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { ContextFieldStrategiesSchema } from '../openapi/spec/context-field-strategies-schema'; import { ContextFieldStrategiesSchema } from '../openapi/spec/context-field-strategies-schema';
import { IFeatureStrategy, IFlagResolver } from '../types'; import { IFeatureStrategy, IFlagResolver } from '../types';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import EventService from './event-service';
const { contextSchema, nameSchema } = require('./context-schema'); const { contextSchema, nameSchema } = require('./context-schema');
const NameExistsError = require('../error/name-exists-error'); const NameExistsError = require('../error/name-exists-error');
@ -24,7 +24,7 @@ const {
class ContextService { class ContextService {
private projectStore: IProjectStore; private projectStore: IProjectStore;
private eventStore: IEventStore; private eventService: EventService;
private contextFieldStore: IContextFieldStore; private contextFieldStore: IContextFieldStore;
@ -39,25 +39,22 @@ class ContextService {
constructor( constructor(
{ {
projectStore, projectStore,
eventStore,
contextFieldStore, contextFieldStore,
featureStrategiesStore, featureStrategiesStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
| 'projectStore' 'projectStore' | 'contextFieldStore' | 'featureStrategiesStore'
| 'eventStore'
| 'contextFieldStore'
| 'featureStrategiesStore'
>, >,
{ {
getLogger, getLogger,
flagResolver, flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>, }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
eventService: EventService,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
) { ) {
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.projectStore = projectStore; this.projectStore = projectStore;
this.eventStore = eventStore; this.eventService = eventService;
this.flagResolver = flagResolver; this.flagResolver = flagResolver;
this.contextFieldStore = contextFieldStore; this.contextFieldStore = contextFieldStore;
this.featureStrategiesStore = featureStrategiesStore; this.featureStrategiesStore = featureStrategiesStore;
@ -120,7 +117,7 @@ class ContextService {
// creations // creations
const createdField = await this.contextFieldStore.create(value); const createdField = await this.contextFieldStore.create(value);
await this.eventStore.store({ await this.eventService.storeEvent({
type: CONTEXT_FIELD_CREATED, type: CONTEXT_FIELD_CREATED,
createdBy: userName, createdBy: userName,
data: contextField, data: contextField,
@ -139,7 +136,7 @@ class ContextService {
// update // update
await this.contextFieldStore.update(value); await this.contextFieldStore.update(value);
await this.eventStore.store({ await this.eventService.storeEvent({
type: CONTEXT_FIELD_UPDATED, type: CONTEXT_FIELD_UPDATED,
createdBy: userName, createdBy: userName,
data: value, data: value,
@ -152,7 +149,7 @@ class ContextService {
// delete // delete
await this.contextFieldStore.delete(name); await this.contextFieldStore.delete(name);
await this.eventStore.store({ await this.eventService.storeEvent({
type: CONTEXT_FIELD_DELETED, type: CONTEXT_FIELD_DELETED,
createdBy: userName, createdBy: userName,
data: { name }, data: { name },

View File

@ -1,21 +1,29 @@
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IFeatureTagStore, 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 { IEventList } from '../types/events'; import { IBaseEvent, IEventList } from '../types/events';
import { SearchEventsSchema } from '../openapi/spec/search-events-schema'; import { SearchEventsSchema } from '../openapi/spec/search-events-schema';
import EventEmitter from 'events';
import { ITag } from '../types';
export default class EventService { export default class EventService {
private logger: Logger; private logger: Logger;
private eventStore: IEventStore; private eventStore: IEventStore;
private featureTagStore: IFeatureTagStore;
constructor( constructor(
{ eventStore }: Pick<IUnleashStores, 'eventStore'>, {
eventStore,
featureTagStore,
}: Pick<IUnleashStores, 'eventStore' | 'featureTagStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) { ) {
this.logger = getLogger('services/event-service.ts'); this.logger = getLogger('services/event-service.ts');
this.eventStore = eventStore; this.eventStore = eventStore;
this.featureTagStore = featureTagStore;
} }
async getEvents(): Promise<IEventList> { async getEvents(): Promise<IEventList> {
@ -35,4 +43,53 @@ export default class EventService {
totalEvents, totalEvents,
}; };
} }
async onEvent(
eventName: string | symbol,
listener: (...args: any[]) => void,
): Promise<EventEmitter> {
return this.eventStore.on(eventName, listener);
}
private async enhanceEventsWithTags(
events: IBaseEvent[],
): Promise<IBaseEvent[]> {
const featureNamesSet = new Set<string>();
for (const event of events) {
if (event.featureName && !event.tags) {
featureNamesSet.add(event.featureName);
}
}
const featureTagsMap: Map<string, ITag[]> = new Map();
const allTagsInFeatures = await this.featureTagStore.getAllByFeatures(
Array.from(featureNamesSet),
);
for (const tag of allTagsInFeatures) {
const featureTags = featureTagsMap.get(tag.featureName) || [];
featureTags.push({ value: tag.tagValue, type: tag.tagType });
featureTagsMap.set(tag.featureName, featureTags);
}
for (const event of events) {
if (event.featureName && !event.tags) {
event.tags = featureTagsMap.get(event.featureName);
}
}
return events;
}
async storeEvent(event: IBaseEvent): Promise<void> {
return this.storeEvents([event]);
}
async storeEvents(events: IBaseEvent[]): Promise<void> {
let enhancedEvents = events;
for (const enhancer of [this.enhanceEventsWithTags.bind(this)]) {
enhancedEvents = await enhancer(enhancedEvents);
}
return this.eventStore.batchStore(enhancedEvents);
}
} }

View File

@ -1,9 +1,5 @@
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { import { IFavoriteProjectsStore, IUnleashStores } from '../types/stores';
IEventStore,
IFavoriteProjectsStore,
IUnleashStores,
} from '../types/stores';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { IFavoriteFeaturesStore } from '../types/stores/favorite-features'; import { IFavoriteFeaturesStore } from '../types/stores/favorite-features';
import { IFavoriteFeature, IFavoriteProject } from '../types/favorites'; import { IFavoriteFeature, IFavoriteProject } from '../types/favorites';
@ -16,6 +12,7 @@ import {
import User from '../types/user'; import User from '../types/user';
import { extractUsernameFromUser } from '../util'; import { extractUsernameFromUser } from '../util';
import { IFavoriteProjectKey } from '../types/stores/favorite-projects'; import { IFavoriteProjectKey } from '../types/stores/favorite-projects';
import EventService from './event-service';
export interface IFavoriteFeatureProps { export interface IFavoriteFeatureProps {
feature: string; feature: string;
@ -36,24 +33,24 @@ export class FavoritesService {
private favoriteProjectsStore: IFavoriteProjectsStore; private favoriteProjectsStore: IFavoriteProjectsStore;
private eventStore: IEventStore; private eventService: EventService;
constructor( constructor(
{ {
favoriteFeaturesStore, favoriteFeaturesStore,
favoriteProjectsStore, favoriteProjectsStore,
eventStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
'favoriteFeaturesStore' | 'favoriteProjectsStore' | 'eventStore' 'favoriteFeaturesStore' | 'favoriteProjectsStore'
>, >,
config: IUnleashConfig, config: IUnleashConfig,
eventService: EventService,
) { ) {
this.config = config; this.config = config;
this.logger = config.getLogger('services/favorites-service.ts'); this.logger = config.getLogger('services/favorites-service.ts');
this.favoriteFeaturesStore = favoriteFeaturesStore; this.favoriteFeaturesStore = favoriteFeaturesStore;
this.favoriteProjectsStore = favoriteProjectsStore; this.favoriteProjectsStore = favoriteProjectsStore;
this.eventStore = eventStore; this.eventService = eventService;
} }
async favoriteFeature({ async favoriteFeature({
@ -64,8 +61,9 @@ export class FavoritesService {
feature: feature, feature: feature,
userId: user.id, userId: user.id,
}); });
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_FAVORITED, type: FEATURE_FAVORITED,
featureName: feature,
createdBy: extractUsernameFromUser(user), createdBy: extractUsernameFromUser(user),
data: { data: {
feature, feature,
@ -82,8 +80,9 @@ export class FavoritesService {
feature: feature, feature: feature,
userId: user.id, userId: user.id,
}); });
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_UNFAVORITED, type: FEATURE_UNFAVORITED,
featureName: feature,
createdBy: extractUsernameFromUser(user), createdBy: extractUsernameFromUser(user),
data: { data: {
feature, feature,
@ -100,7 +99,7 @@ export class FavoritesService {
project, project,
userId: user.id, userId: user.id,
}); });
await this.eventStore.store({ await this.eventService.storeEvent({
type: PROJECT_FAVORITED, type: PROJECT_FAVORITED,
createdBy: extractUsernameFromUser(user), createdBy: extractUsernameFromUser(user),
data: { data: {
@ -118,7 +117,7 @@ export class FavoritesService {
project: project, project: project,
userId: user.id, userId: user.id,
}); });
await this.eventStore.store({ await this.eventService.storeEvent({
type: PROJECT_UNFAVORITED, type: PROJECT_UNFAVORITED,
createdBy: extractUsernameFromUser(user), createdBy: extractUsernameFromUser(user),
data: { data: {

View File

@ -11,6 +11,8 @@ import { IChangeRequestAccessReadModel } from 'lib/features/change-request-acces
import { ISegmentService } from 'lib/segments/segment-service-interface'; import { ISegmentService } from 'lib/segments/segment-service-interface';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type'; import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
test('Should only store events for potentially stale on', async () => { test('Should only store events for potentially stale on', async () => {
expect.assertions(2); expect.assertions(2);
@ -20,16 +22,11 @@ test('Should only store events for potentially stale on', async () => {
]; ];
const config = createTestConfig(); const config = createTestConfig();
const featureToggleService = new FeatureToggleService( const eventService = new EventService(
{ {
featureToggleStore: { // @ts-expect-error
updatePotentiallyStaleFeatures: () => featureUpdates,
},
featureTagStore: {
getAllTagsForFeature: () => [],
},
eventStore: { eventStore: {
batchStore: (events: IEvent[]) => { batchStore: async (events: IEvent[]) => {
expect(events.length).toBe(1); expect(events.length).toBe(1);
const [event1] = events; const [event1] = events;
@ -40,6 +37,19 @@ test('Should only store events for potentially stale on', async () => {
}); });
}, },
}, },
featureTagStore: new FakeFeatureTagStore(),
},
config,
);
const featureToggleService = new FeatureToggleService(
{
featureToggleStore: {
updatePotentiallyStaleFeatures: () => featureUpdates,
},
featureTagStore: {
getAllTagsForFeature: () => [],
},
} as unknown as IUnleashStores, } as unknown as IUnleashStores,
{ {
...config, ...config,
@ -50,6 +60,7 @@ test('Should only store events for potentially stale on', async () => {
} as unknown as IUnleashConfig, } as unknown as IUnleashConfig,
{} as ISegmentService, {} as ISegmentService,
{} as AccessService, {} as AccessService,
eventService,
{} as IChangeRequestAccessReadModel, {} as IChangeRequestAccessReadModel,
{} as IPrivateProjectChecker, {} as IPrivateProjectChecker,
{} as IDependentFeaturesReadModel, {} as IDependentFeaturesReadModel,

View File

@ -8,10 +8,10 @@ import {
IFeatureTag, IFeatureTag,
IFeatureTagStore, IFeatureTagStore,
} from '../types/stores/feature-tag-store'; } from '../types/stores/feature-tag-store';
import { IEventStore } from '../types/stores/event-store';
import { ITagStore } from '../types/stores/tag-store'; import { ITagStore } from '../types/stores/tag-store';
import { ITag } from '../types/model'; import { ITag } from '../types/model';
import { BadDataError, FOREIGN_KEY_VIOLATION } from '../../lib/error'; import { BadDataError, FOREIGN_KEY_VIOLATION } from '../../lib/error';
import EventService from './event-service';
class FeatureTagService { class FeatureTagService {
private tagStore: ITagStore; private tagStore: ITagStore;
@ -20,7 +20,7 @@ class FeatureTagService {
private featureToggleStore: IFeatureToggleStore; private featureToggleStore: IFeatureToggleStore;
private eventStore: IEventStore; private eventService: EventService;
private logger: Logger; private logger: Logger;
@ -28,19 +28,19 @@ class FeatureTagService {
{ {
tagStore, tagStore,
featureTagStore, featureTagStore,
eventStore,
featureToggleStore, featureToggleStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
'tagStore' | 'featureTagStore' | 'eventStore' | 'featureToggleStore' 'tagStore' | 'featureTagStore' | 'featureToggleStore'
>, >,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.logger = getLogger('/services/feature-tag-service.ts'); this.logger = getLogger('/services/feature-tag-service.ts');
this.tagStore = tagStore; this.tagStore = tagStore;
this.featureTagStore = featureTagStore; this.featureTagStore = featureTagStore;
this.featureToggleStore = featureToggleStore; this.featureToggleStore = featureToggleStore;
this.eventStore = eventStore; this.eventService = eventService;
} }
async listTags(featureName: string): Promise<ITag[]> { async listTags(featureName: string): Promise<ITag[]> {
@ -62,7 +62,7 @@ class FeatureTagService {
await this.createTagIfNeeded(validatedTag, userName); await this.createTagIfNeeded(validatedTag, userName);
await this.featureTagStore.tagFeature(featureName, validatedTag); await this.featureTagStore.tagFeature(featureName, validatedTag);
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_TAGGED, type: FEATURE_TAGGED,
createdBy: userName, createdBy: userName,
featureName, featureName,
@ -126,7 +126,10 @@ class FeatureTagService {
})), })),
); );
await this.eventStore.batchStore([...creationEvents, ...removalEvents]); await this.eventService.storeEvents([
...creationEvents,
...removalEvents,
]);
} }
async createTagIfNeeded(tag: ITag, userName: string): Promise<void> { async createTagIfNeeded(tag: ITag, userName: string): Promise<void> {
@ -136,7 +139,7 @@ class FeatureTagService {
if (error instanceof NotFoundError) { if (error instanceof NotFoundError) {
try { try {
await this.tagStore.createTag(tag); await this.tagStore.createTag(tag);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_CREATED, type: TAG_CREATED,
createdBy: userName, createdBy: userName,
data: tag, data: tag,
@ -159,13 +162,17 @@ class FeatureTagService {
userName: string, userName: string,
): Promise<void> { ): Promise<void> {
const featureToggle = await this.featureToggleStore.get(featureName); const featureToggle = await this.featureToggleStore.get(featureName);
const tags = await this.featureTagStore.getAllTagsForFeature(
featureName,
);
await this.featureTagStore.untagFeature(featureName, tag); await this.featureTagStore.untagFeature(featureName, tag);
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_UNTAGGED, type: FEATURE_UNTAGGED,
createdBy: userName, createdBy: userName,
featureName, featureName,
project: featureToggle.project, project: featureToggle.project,
data: tag, data: tag,
tags,
}); });
} }
} }

View File

@ -21,7 +21,6 @@ import {
FeatureVariantEvent, FeatureVariantEvent,
IConstraint, IConstraint,
IDependency, IDependency,
IEventStore,
IFeatureEnvironmentInfo, IFeatureEnvironmentInfo,
IFeatureEnvironmentStore, IFeatureEnvironmentStore,
IFeatureNaming, IFeatureNaming,
@ -99,6 +98,7 @@ import { IChangeRequestAccessReadModel } from '../features/change-request-access
import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation'; import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type'; import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
import EventService from './event-service';
interface IFeatureContext { interface IFeatureContext {
featureName: string; featureName: string;
@ -146,14 +146,14 @@ class FeatureToggleService {
private projectStore: IProjectStore; private projectStore: IProjectStore;
private eventStore: IEventStore;
private contextFieldStore: IContextFieldStore; private contextFieldStore: IContextFieldStore;
private segmentService: ISegmentService; private segmentService: ISegmentService;
private accessService: AccessService; private accessService: AccessService;
private eventService: EventService;
private flagResolver: IFlagResolver; private flagResolver: IFlagResolver;
private changeRequestAccessReadModel: IChangeRequestAccessReadModel; private changeRequestAccessReadModel: IChangeRequestAccessReadModel;
@ -168,7 +168,6 @@ class FeatureToggleService {
featureToggleStore, featureToggleStore,
featureToggleClientStore, featureToggleClientStore,
projectStore, projectStore,
eventStore,
featureTagStore, featureTagStore,
featureEnvironmentStore, featureEnvironmentStore,
contextFieldStore, contextFieldStore,
@ -179,7 +178,6 @@ class FeatureToggleService {
| 'featureToggleStore' | 'featureToggleStore'
| 'featureToggleClientStore' | 'featureToggleClientStore'
| 'projectStore' | 'projectStore'
| 'eventStore'
| 'featureTagStore' | 'featureTagStore'
| 'featureEnvironmentStore' | 'featureEnvironmentStore'
| 'contextFieldStore' | 'contextFieldStore'
@ -191,6 +189,7 @@ class FeatureToggleService {
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>, }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
segmentService: ISegmentService, segmentService: ISegmentService,
accessService: AccessService, accessService: AccessService,
eventService: EventService,
changeRequestAccessReadModel: IChangeRequestAccessReadModel, changeRequestAccessReadModel: IChangeRequestAccessReadModel,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
dependentFeaturesReadModel: IDependentFeaturesReadModel, dependentFeaturesReadModel: IDependentFeaturesReadModel,
@ -202,11 +201,11 @@ class FeatureToggleService {
this.featureToggleClientStore = featureToggleClientStore; this.featureToggleClientStore = featureToggleClientStore;
this.tagStore = featureTagStore; this.tagStore = featureTagStore;
this.projectStore = projectStore; this.projectStore = projectStore;
this.eventStore = eventStore;
this.featureEnvironmentStore = featureEnvironmentStore; this.featureEnvironmentStore = featureEnvironmentStore;
this.contextFieldStore = contextFieldStore; this.contextFieldStore = contextFieldStore;
this.segmentService = segmentService; this.segmentService = segmentService;
this.accessService = accessService; this.accessService = accessService;
this.eventService = eventService;
this.flagResolver = flagResolver; this.flagResolver = flagResolver;
this.changeRequestAccessReadModel = changeRequestAccessReadModel; this.changeRequestAccessReadModel = changeRequestAccessReadModel;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
@ -394,15 +393,12 @@ class FeatureToggleService {
); );
if (featureToggle.stale !== newDocument.stale) { if (featureToggle.stale !== newDocument.stale) {
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureStaleEvent({ new FeatureStaleEvent({
stale: newDocument.stale, stale: newDocument.stale,
project, project,
featureName, featureName,
createdBy, createdBy,
tags,
}), }),
); );
} }
@ -502,7 +498,6 @@ class FeatureToggleService {
const eventData: StrategyIds = { strategyIds: newOrder }; const eventData: StrategyIds = { strategyIds: newOrder };
const tags = await this.tagStore.getAllTagsForFeature(featureName);
const event = new StrategiesOrderChangedEvent({ const event = new StrategiesOrderChangedEvent({
featureName, featureName,
environment, environment,
@ -510,9 +505,8 @@ class FeatureToggleService {
createdBy, createdBy,
preData: eventPreData, preData: eventPreData,
data: eventData, data: eventData,
tags: tags,
}); });
await this.eventStore.store(event); await this.eventService.storeEvent(event);
} }
async createStrategy( async createStrategy(
@ -606,16 +600,13 @@ class FeatureToggleService {
segments, segments,
); );
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureStrategyAddEvent({ new FeatureStrategyAddEvent({
project: projectId, project: projectId,
featureName, featureName,
createdBy, createdBy,
environment, environment,
data: strategy, data: strategy,
tags,
}), }),
); );
return strategy; return strategy;
@ -724,13 +715,12 @@ class FeatureToggleService {
); );
// Store event! // Store event!
const tags = await this.tagStore.getAllTagsForFeature(featureName);
const data = this.featureStrategyToPublic(strategy, segments); const data = this.featureStrategyToPublic(strategy, segments);
const preData = this.featureStrategyToPublic( const preData = this.featureStrategyToPublic(
existingStrategy, existingStrategy,
segments, segments,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureStrategyUpdateEvent({ new FeatureStrategyUpdateEvent({
project: projectId, project: projectId,
featureName, featureName,
@ -738,7 +728,6 @@ class FeatureToggleService {
createdBy: userName, createdBy: userName,
data, data,
preData, preData,
tags,
}), }),
); );
await this.optionallyDisableFeature( await this.optionallyDisableFeature(
@ -770,7 +759,6 @@ class FeatureToggleService {
id, id,
existingStrategy, existingStrategy,
); );
const tags = await this.tagStore.getAllTagsForFeature(featureName);
const segments = await this.segmentService.getByStrategy( const segments = await this.segmentService.getByStrategy(
strategy.id, strategy.id,
); );
@ -779,7 +767,7 @@ class FeatureToggleService {
existingStrategy, existingStrategy,
segments, segments,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureStrategyUpdateEvent({ new FeatureStrategyUpdateEvent({
featureName, featureName,
project: projectId, project: projectId,
@ -787,7 +775,6 @@ class FeatureToggleService {
createdBy: userName, createdBy: userName,
data, data,
preData, preData,
tags,
}), }),
); );
return data; return data;
@ -852,17 +839,15 @@ class FeatureToggleService {
); );
} }
const tags = await this.tagStore.getAllTagsForFeature(featureName);
const preData = this.featureStrategyToPublic(existingStrategy); const preData = this.featureStrategyToPublic(existingStrategy);
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureStrategyRemoveEvent({ new FeatureStrategyRemoveEvent({
featureName, featureName,
project: projectId, project: projectId,
environment, environment,
createdBy, createdBy,
preData, preData,
tags,
}), }),
); );
@ -1125,15 +1110,12 @@ class FeatureToggleService {
); );
} }
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureCreatedEvent({ new FeatureCreatedEvent({
featureName, featureName,
createdBy, createdBy,
project: projectId, project: projectId,
data: createdToggle, data: createdToggle,
tags,
}), }),
); );
@ -1283,16 +1265,13 @@ class FeatureToggleService {
name: featureName, name: featureName,
}); });
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureMetadataUpdateEvent({ new FeatureMetadataUpdateEvent({
createdBy: userName, createdBy: userName,
data: featureToggle, data: featureToggle,
preData, preData,
featureName, featureName,
project: projectId, project: projectId,
tags,
}), }),
); );
return featureToggle; return featureToggle;
@ -1419,15 +1398,13 @@ class FeatureToggleService {
const { project } = feature; const { project } = feature;
feature.stale = isStale; feature.stale = isStale;
await this.featureToggleStore.update(project, feature); await this.featureToggleStore.update(project, feature);
const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureStaleEvent({ new FeatureStaleEvent({
stale: isStale, stale: isStale,
project, project,
featureName, featureName,
createdBy, createdBy,
tags,
}), }),
); );
@ -1449,13 +1426,12 @@ class FeatureToggleService {
} }
await this.featureToggleStore.archive(featureName); await this.featureToggleStore.archive(featureName);
const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureArchivedEvent({ new FeatureArchivedEvent({
featureName, featureName,
createdBy, createdBy,
project: feature.project, project: feature.project,
tags,
}), }),
); );
} }
@ -1471,20 +1447,14 @@ class FeatureToggleService {
featureNames, featureNames,
); );
await this.featureToggleStore.batchArchive(featureNames); await this.featureToggleStore.batchArchive(featureNames);
const tags = await this.tagStore.getAllByFeatures(featureNames);
await this.eventStore.batchStore( await this.eventService.storeEvents(
features.map( features.map(
(feature) => (feature) =>
new FeatureArchivedEvent({ new FeatureArchivedEvent({
featureName: feature.name, featureName: feature.name,
createdBy, createdBy,
project: feature.project, project: feature.project,
tags: tags
.filter((tag) => tag.featureName === feature.name)
.map((tag) => ({
value: tag.tagValue,
type: tag.tagType,
})),
}), }),
), ),
); );
@ -1508,8 +1478,8 @@ class FeatureToggleService {
(feature) => feature.name, (feature) => feature.name,
); );
await this.featureToggleStore.batchStale(relevantFeatureNames, stale); await this.featureToggleStore.batchStale(relevantFeatureNames, stale);
const tags = await this.tagStore.getAllByFeatures(relevantFeatureNames);
await this.eventStore.batchStore( await this.eventService.storeEvents(
relevantFeatures.map( relevantFeatures.map(
(feature) => (feature) =>
new FeatureStaleEvent({ new FeatureStaleEvent({
@ -1517,12 +1487,6 @@ class FeatureToggleService {
project: projectId, project: projectId,
featureName: feature.name, featureName: feature.name,
createdBy, createdBy,
tags: tags
.filter((tag) => tag.featureName === feature.name)
.map((tag) => ({
value: tag.tagValue,
type: tag.tagType,
})),
}), }),
), ),
); );
@ -1667,15 +1631,13 @@ class FeatureToggleService {
const feature = await this.featureToggleStore.get(featureName); const feature = await this.featureToggleStore.get(featureName);
if (updatedEnvironmentStatus > 0) { if (updatedEnvironmentStatus > 0) {
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled, enabled,
project, project,
featureName, featureName,
environment, environment,
createdBy, createdBy,
tags,
}), }),
); );
} }
@ -1687,17 +1649,15 @@ class FeatureToggleService {
featureName: string, featureName: string,
createdBy: string, createdBy: string,
): Promise<FeatureToggleLegacy> { ): Promise<FeatureToggleLegacy> {
const tags = await this.tagStore.getAllTagsForFeature(featureName);
const feature = await this.getFeatureToggleLegacy(featureName); const feature = await this.getFeatureToggleLegacy(featureName);
// Legacy event. Will not be used from v4.3. // Legacy event. Will not be used from v4.3.
// We do not include 'preData' on purpose. // We do not include 'preData' on purpose.
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_UPDATED, type: FEATURE_UPDATED,
createdBy, createdBy,
featureName, featureName,
data: feature, data: feature,
tags,
project: feature.project, project: feature.project,
}); });
return feature; return feature;
@ -1759,14 +1719,12 @@ class FeatureToggleService {
feature.project = newProject; feature.project = newProject;
await this.featureToggleStore.update(newProject, feature); await this.featureToggleStore.update(newProject, feature);
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new FeatureChangeProjectEvent({ new FeatureChangeProjectEvent({
createdBy, createdBy,
oldProject, oldProject,
newProject, newProject,
featureName, featureName,
tags,
}), }),
); );
} }
@ -1780,7 +1738,8 @@ class FeatureToggleService {
const toggle = await this.featureToggleStore.get(featureName); const toggle = await this.featureToggleStore.get(featureName);
const tags = await this.tagStore.getAllTagsForFeature(featureName); const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.featureToggleStore.delete(featureName); await this.featureToggleStore.delete(featureName);
await this.eventStore.store(
await this.eventService.storeEvent(
new FeatureDeletedEvent({ new FeatureDeletedEvent({
featureName, featureName,
project: toggle.project, project: toggle.project,
@ -1809,7 +1768,8 @@ class FeatureToggleService {
); );
const tags = await this.tagStore.getAllByFeatures(eligibleFeatureNames); const tags = await this.tagStore.getAllByFeatures(eligibleFeatureNames);
await this.featureToggleStore.batchDelete(eligibleFeatureNames); await this.featureToggleStore.batchDelete(eligibleFeatureNames);
await this.eventStore.batchStore(
await this.eventService.storeEvents(
eligibleFeatures.map( eligibleFeatures.map(
(feature) => (feature) =>
new FeatureDeletedEvent({ new FeatureDeletedEvent({
@ -1844,21 +1804,15 @@ class FeatureToggleService {
const eligibleFeatureNames = eligibleFeatures.map( const eligibleFeatureNames = eligibleFeatures.map(
(toggle) => toggle.name, (toggle) => toggle.name,
); );
const tags = await this.tagStore.getAllByFeatures(eligibleFeatureNames);
await this.featureToggleStore.batchRevive(eligibleFeatureNames); await this.featureToggleStore.batchRevive(eligibleFeatureNames);
await this.eventStore.batchStore(
await this.eventService.storeEvents(
eligibleFeatures.map( eligibleFeatures.map(
(feature) => (feature) =>
new FeatureRevivedEvent({ new FeatureRevivedEvent({
featureName: feature.name, featureName: feature.name,
createdBy, createdBy,
project: feature.project, project: feature.project,
tags: tags
.filter((tag) => tag.featureName === feature.name)
.map((tag) => ({
value: tag.tagValue,
type: tag.tagType,
})),
}), }),
), ),
); );
@ -1867,13 +1821,12 @@ class FeatureToggleService {
// TODO: add project id. // TODO: add project id.
async reviveFeature(featureName: string, createdBy: string): Promise<void> { async reviveFeature(featureName: string, createdBy: string): Promise<void> {
const toggle = await this.featureToggleStore.revive(featureName); const toggle = await this.featureToggleStore.revive(featureName);
const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureRevivedEvent({ new FeatureRevivedEvent({
createdBy, createdBy,
featureName, featureName,
project: toggle.project, project: toggle.project,
tags,
}), }),
); );
} }
@ -1985,13 +1938,12 @@ class FeatureToggleService {
featureName, featureName,
fixedVariants, fixedVariants,
); );
const tags = await this.tagStore.getAllTagsForFeature(featureName);
await this.eventStore.store( await this.eventService.storeEvent(
new FeatureVariantEvent({ new FeatureVariantEvent({
project, project,
featureName, featureName,
createdBy, createdBy,
tags,
oldVariants, oldVariants,
newVariants: featureToggle.variants as IVariant[], newVariants: featureToggle.variants as IVariant[],
}), }),
@ -2019,9 +1971,7 @@ class FeatureToggleService {
).variants || ).variants ||
[]; [];
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvent(
await this.eventStore.store(
new EnvironmentVariantEvent({ new EnvironmentVariantEvent({
featureName, featureName,
environment, environment,
@ -2029,7 +1979,6 @@ class FeatureToggleService {
createdBy: user, createdBy: user,
oldVariants: theOldVariants, oldVariants: theOldVariants,
newVariants: fixedVariants, newVariants: fixedVariants,
tags,
}), }),
); );
await this.featureEnvironmentStore.setVariantsToFeatureEnvironments( await this.featureEnvironmentStore.setVariantsToFeatureEnvironments(
@ -2096,9 +2045,7 @@ class FeatureToggleService {
oldVariants[env] = featureEnv.variants || []; oldVariants[env] = featureEnv.variants || [];
} }
const tags = await this.tagStore.getAllTagsForFeature(featureName); await this.eventService.storeEvents(
await this.eventStore.batchStore(
environments.map( environments.map(
(environment) => (environment) =>
new EnvironmentVariantEvent({ new EnvironmentVariantEvent({
@ -2108,7 +2055,6 @@ class FeatureToggleService {
createdBy: user, createdBy: user,
oldVariants: oldVariants[environment], oldVariants: oldVariants[environment],
newVariants: fixedVariants, newVariants: fixedVariants,
tags,
}), }),
), ),
); );
@ -2214,22 +2160,18 @@ class FeatureToggleService {
async updatePotentiallyStaleFeatures(): Promise<void> { async updatePotentiallyStaleFeatures(): Promise<void> {
const potentiallyStaleFeatures = const potentiallyStaleFeatures =
await this.featureToggleStore.updatePotentiallyStaleFeatures(); await this.featureToggleStore.updatePotentiallyStaleFeatures();
if (potentiallyStaleFeatures.length > 0) { if (potentiallyStaleFeatures.length > 0) {
return this.eventStore.batchStore( return this.eventService.storeEvents(
await Promise.all( potentiallyStaleFeatures
potentiallyStaleFeatures .filter((feature) => feature.potentiallyStale)
.filter((feature) => feature.potentiallyStale) .map(
.map( ({ name, project }) =>
async ({ name, project }) => new PotentiallyStaleOnEvent({
new PotentiallyStaleOnEvent({ featureName: name,
featureName: name, project,
project, }),
tags: await this.tagStore.getAllTagsForFeature( ),
name,
),
}),
),
),
); );
} }
} }

View File

@ -12,30 +12,28 @@ import { IGroupStore } from '../types/stores/group-store';
import { Logger } from '../logger'; import { Logger } from '../logger';
import BadDataError from '../error/bad-data-error'; import BadDataError from '../error/bad-data-error';
import { GROUP_CREATED, GROUP_DELETED, GROUP_UPDATED } from '../types/events'; import { GROUP_CREATED, GROUP_DELETED, GROUP_UPDATED } from '../types/events';
import { IEventStore } from '../types/stores/event-store';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
import { IAccountStore } from '../types/stores/account-store'; import { IAccountStore } from '../types/stores/account-store';
import { IUser } from '../types/user'; import { IUser } from '../types/user';
import EventService from './event-service';
export class GroupService { export class GroupService {
private groupStore: IGroupStore; private groupStore: IGroupStore;
private eventStore: IEventStore; private eventService: EventService;
private accountStore: IAccountStore; private accountStore: IAccountStore;
private logger: Logger; private logger: Logger;
constructor( constructor(
stores: Pick< stores: Pick<IUnleashStores, 'groupStore' | 'accountStore'>,
IUnleashStores,
'groupStore' | 'eventStore' | 'accountStore'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.logger = getLogger('service/group-service.js'); this.logger = getLogger('service/group-service.js');
this.groupStore = stores.groupStore; this.groupStore = stores.groupStore;
this.eventStore = stores.eventStore; this.eventService = eventService;
this.accountStore = stores.accountStore; this.accountStore = stores.accountStore;
} }
@ -96,7 +94,7 @@ export class GroupService {
userName, userName,
); );
await this.eventStore.store({ await this.eventService.storeEvent({
type: GROUP_CREATED, type: GROUP_CREATED,
createdBy: userName, createdBy: userName,
data: group, data: group,
@ -133,7 +131,7 @@ export class GroupService {
userName, userName,
); );
await this.eventStore.store({ await this.eventService.storeEvent({
type: GROUP_UPDATED, type: GROUP_UPDATED,
createdBy: userName, createdBy: userName,
data: newGroup, data: newGroup,
@ -175,7 +173,7 @@ export class GroupService {
await this.groupStore.delete(id); await this.groupStore.delete(id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: GROUP_DELETED, type: GROUP_DELETED,
createdBy: userName, createdBy: userName,
data: group, data: group,

View File

@ -165,9 +165,10 @@ export const createServices = (
config: IUnleashConfig, config: IUnleashConfig,
db?: Db, db?: Db,
): IUnleashServices => { ): IUnleashServices => {
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const apiTokenService = new ApiTokenService(stores, config); const apiTokenService = new ApiTokenService(stores, config, eventService);
const lastSeenService = new LastSeenService(stores, config); const lastSeenService = new LastSeenService(stores, config);
const clientMetricsServiceV2 = new ClientMetricsServiceV2( const clientMetricsServiceV2 = new ClientMetricsServiceV2(
stores, stores,
@ -184,23 +185,29 @@ export const createServices = (
const contextService = new ContextService( const contextService = new ContextService(
stores, stores,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const eventService = new EventService(stores, config);
const featureTypeService = new FeatureTypeService(stores, config); const featureTypeService = new FeatureTypeService(stores, config);
const resetTokenService = new ResetTokenService(stores, config); const resetTokenService = new ResetTokenService(stores, config);
const stateService = new StateService(stores, config); const stateService = new StateService(stores, config, eventService);
const strategyService = new StrategyService(stores, config); const strategyService = new StrategyService(stores, config, eventService);
const tagService = new TagService(stores, config); const tagService = new TagService(stores, config, eventService);
const tagTypeService = new TagTypeService(stores, config); const tagTypeService = new TagTypeService(stores, config, eventService);
const addonService = new AddonService(stores, config, tagTypeService); const addonService = new AddonService(
stores,
config,
tagTypeService,
eventService,
);
const sessionService = new SessionService(stores, config); const sessionService = new SessionService(stores, config);
const settingService = new SettingService(stores, config); const settingService = new SettingService(stores, config, eventService);
const userService = new UserService(stores, config, { const userService = new UserService(stores, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -217,6 +224,7 @@ export const createServices = (
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
@ -230,13 +238,18 @@ export const createServices = (
config, config,
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );
const environmentService = new EnvironmentService(stores, config); const environmentService = new EnvironmentService(stores, config);
const featureTagService = new FeatureTagService(stores, config); const featureTagService = new FeatureTagService(
const favoritesService = new FavoritesService(stores, config); stores,
config,
eventService,
);
const favoritesService = new FavoritesService(stores, config, eventService);
const projectService = new ProjectService( const projectService = new ProjectService(
stores, stores,
config, config,
@ -244,6 +257,7 @@ export const createServices = (
featureToggleServiceV2, featureToggleServiceV2,
groupService, groupService,
favoritesService, favoritesService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
const projectHealthService = new ProjectHealthService( const projectHealthService = new ProjectHealthService(
@ -286,12 +300,13 @@ export const createServices = (
const edgeService = new EdgeService(stores, config); const edgeService = new EdgeService(stores, config);
const patService = new PatService(stores, config); const patService = new PatService(stores, config, eventService);
const publicSignupTokenService = new PublicSignupTokenService( const publicSignupTokenService = new PublicSignupTokenService(
stores, stores,
config, config,
userService, userService,
eventService,
); );
const instanceStatsService = new InstanceStatsService( const instanceStatsService = new InstanceStatsService(

View File

@ -1,7 +1,6 @@
import { IUnleashConfig, IUnleashStores } from '../types'; import { IUnleashConfig, IUnleashStores } from '../types';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { IPatStore } from '../types/stores/pat-store'; import { IPatStore } from '../types/stores/pat-store';
import { IEventStore } from '../types/stores/event-store';
import { PAT_CREATED, PAT_DELETED } from '../types/events'; import { PAT_CREATED, PAT_DELETED } from '../types/events';
import { IPat } from '../types/models/pat'; import { IPat } from '../types/models/pat';
import crypto from 'crypto'; import crypto from 'crypto';
@ -10,6 +9,7 @@ import BadDataError from '../error/bad-data-error';
import NameExistsError from '../error/name-exists-error'; import NameExistsError from '../error/name-exists-error';
import { OperationDeniedError } from '../error/operation-denied-error'; import { OperationDeniedError } from '../error/operation-denied-error';
import { PAT_LIMIT } from '../util/constants'; import { PAT_LIMIT } from '../util/constants';
import EventService from './event-service';
export default class PatService { export default class PatService {
private config: IUnleashConfig; private config: IUnleashConfig;
@ -18,19 +18,17 @@ export default class PatService {
private patStore: IPatStore; private patStore: IPatStore;
private eventStore: IEventStore; private eventService: EventService;
constructor( constructor(
{ { patStore }: Pick<IUnleashStores, 'patStore'>,
patStore,
eventStore,
}: Pick<IUnleashStores, 'patStore' | 'eventStore'>,
config: IUnleashConfig, config: IUnleashConfig,
eventService: EventService,
) { ) {
this.config = config; this.config = config;
this.logger = config.getLogger('services/pat-service.ts'); this.logger = config.getLogger('services/pat-service.ts');
this.patStore = patStore; this.patStore = patStore;
this.eventStore = eventStore; this.eventService = eventService;
} }
async createPat(pat: IPat, forUserId: number, editor: User): Promise<IPat> { async createPat(pat: IPat, forUserId: number, editor: User): Promise<IPat> {
@ -40,7 +38,7 @@ export default class PatService {
const newPat = await this.patStore.create(pat); const newPat = await this.patStore.create(pat);
pat.secret = '***'; pat.secret = '***';
await this.eventStore.store({ await this.eventService.storeEvent({
type: PAT_CREATED, type: PAT_CREATED,
createdBy: editor.email || editor.username, createdBy: editor.email || editor.username,
data: pat, data: pat,
@ -61,7 +59,7 @@ export default class PatService {
const pat = await this.patStore.get(id); const pat = await this.patStore.get(id);
pat.secret = '***'; pat.secret = '***';
await this.eventStore.store({ await this.eventService.storeEvent({
type: PAT_DELETED, type: PAT_DELETED,
createdBy: editor.email || editor.username, createdBy: editor.email || editor.username,
data: pat, data: pat,

View File

@ -64,6 +64,7 @@ import { BadDataError, PermissionError } from '../error';
import { ProjectDoraMetricsSchema } from 'lib/openapi'; import { ProjectDoraMetricsSchema } from 'lib/openapi';
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation'; import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import EventService from './event-service';
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown'; const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
@ -113,6 +114,8 @@ export default class ProjectService {
private favoritesService: FavoritesService; private favoritesService: FavoritesService;
private eventService: EventService;
private projectStatsStore: IProjectStatsStore; private projectStatsStore: IProjectStatsStore;
private flagResolver: IFlagResolver; private flagResolver: IFlagResolver;
@ -145,6 +148,7 @@ export default class ProjectService {
featureToggleService: FeatureToggleService, featureToggleService: FeatureToggleService,
groupService: GroupService, groupService: GroupService,
favoriteService: FavoritesService, favoriteService: FavoritesService,
eventService: EventService,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
) { ) {
this.projectStore = projectStore; this.projectStore = projectStore;
@ -159,6 +163,7 @@ export default class ProjectService {
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.accountStore = accountStore; this.accountStore = accountStore;
this.groupService = groupService; this.groupService = groupService;
this.eventService = eventService;
this.projectStatsStore = projectStatsStore; this.projectStatsStore = projectStatsStore;
this.logger = config.getLogger('services/project-service.js'); this.logger = config.getLogger('services/project-service.js');
this.flagResolver = config.flagResolver; this.flagResolver = config.flagResolver;
@ -246,7 +251,7 @@ export default class ProjectService {
await this.accessService.createDefaultProjectRoles(user, data.id); await this.accessService.createDefaultProjectRoles(user, data.id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: PROJECT_CREATED, type: PROJECT_CREATED,
createdBy: getCreatedBy(user), createdBy: getCreatedBy(user),
data, data,
@ -284,7 +289,7 @@ export default class ProjectService {
await this.projectStore.updateProjectEnterpriseSettings(updatedProject); await this.projectStore.updateProjectEnterpriseSettings(updatedProject);
await this.eventStore.store({ await this.eventService.storeEvent({
type: PROJECT_UPDATED, type: PROJECT_UPDATED,
project: updatedProject.id, project: updatedProject.id,
createdBy: getCreatedBy(user), createdBy: getCreatedBy(user),
@ -381,7 +386,7 @@ export default class ProjectService {
await this.projectStore.delete(id); await this.projectStore.delete(id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: PROJECT_DELETED, type: PROJECT_DELETED,
createdBy: getCreatedBy(user), createdBy: getCreatedBy(user),
project: id, project: id,
@ -434,7 +439,7 @@ export default class ProjectService {
await this.accessService.addUserToRole(userId, role.id, projectId); await this.accessService.addUserToRole(userId, role.id, projectId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectUserAddedEvent({ new ProjectUserAddedEvent({
project: projectId, project: projectId,
createdBy: createdBy || 'system-user', createdBy: createdBy || 'system-user',
@ -462,7 +467,7 @@ export default class ProjectService {
const user = await this.accountStore.get(userId); const user = await this.accountStore.get(userId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectUserRemovedEvent({ new ProjectUserRemovedEvent({
project: projectId, project: projectId,
createdBy, createdBy,
@ -488,7 +493,7 @@ export default class ProjectService {
await this.accessService.removeUserAccess(projectId, userId); await this.accessService.removeUserAccess(projectId, userId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessUserRolesDeleted({ new ProjectAccessUserRolesDeleted({
project: projectId, project: projectId,
createdBy, createdBy,
@ -512,7 +517,7 @@ export default class ProjectService {
await this.accessService.removeGroupAccess(projectId, groupId); await this.accessService.removeGroupAccess(projectId, groupId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessUserRolesDeleted({ new ProjectAccessUserRolesDeleted({
project: projectId, project: projectId,
createdBy, createdBy,
@ -547,7 +552,7 @@ export default class ProjectService {
project.id, project.id,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectGroupAddedEvent({ new ProjectGroupAddedEvent({
project: project.id, project: project.id,
createdBy: modifiedBy, createdBy: modifiedBy,
@ -582,7 +587,7 @@ export default class ProjectService {
project.id, project.id,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectGroupRemovedEvent({ new ProjectGroupRemovedEvent({
project: projectId, project: projectId,
createdBy: modifiedBy, createdBy: modifiedBy,
@ -609,7 +614,7 @@ export default class ProjectService {
createdBy, createdBy,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessAddedEvent({ new ProjectAccessAddedEvent({
project: projectId, project: projectId,
createdBy, createdBy,
@ -637,7 +642,7 @@ export default class ProjectService {
createdBy, createdBy,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessAddedEvent({ new ProjectAccessAddedEvent({
project: projectId, project: projectId,
createdBy, createdBy,
@ -665,7 +670,7 @@ export default class ProjectService {
userId, userId,
roles, roles,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessUserRolesUpdated({ new ProjectAccessUserRolesUpdated({
project: projectId, project: projectId,
createdBy: createdByUserName, createdBy: createdByUserName,
@ -697,7 +702,7 @@ export default class ProjectService {
roles, roles,
createdBy, createdBy,
); );
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectAccessGroupRolesUpdated({ new ProjectAccessGroupRolesUpdated({
project: projectId, project: projectId,
createdBy, createdBy,
@ -823,7 +828,7 @@ export default class ProjectService {
); );
const role = await this.findProjectRole(projectId, roleId); const role = await this.findProjectRole(projectId, roleId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectUserUpdateRoleEvent({ new ProjectUserUpdateRoleEvent({
project: projectId, project: projectId,
createdBy, createdBy,
@ -877,7 +882,7 @@ export default class ProjectService {
); );
const role = await this.findProjectGroupRole(projectId, roleId); const role = await this.findProjectGroupRole(projectId, roleId);
await this.eventStore.store( await this.eventService.storeEvent(
new ProjectGroupUpdateRoleEvent({ new ProjectGroupUpdateRoleEvent({
project: projectId, project: projectId,
createdBy, createdBy,

View File

@ -8,7 +8,6 @@ import { IPublicSignupTokenCreate } from '../types/models/public-signup-token';
import { PublicSignupTokenCreateSchema } from '../openapi/spec/public-signup-token-create-schema'; import { PublicSignupTokenCreateSchema } from '../openapi/spec/public-signup-token-create-schema';
import { CreateInvitedUserSchema } from 'lib/openapi/spec/create-invited-user-schema'; import { CreateInvitedUserSchema } from 'lib/openapi/spec/create-invited-user-schema';
import { RoleName } from '../types/model'; import { RoleName } from '../types/model';
import { IEventStore } from '../types/stores/event-store';
import { import {
PublicSignupTokenCreatedEvent, PublicSignupTokenCreatedEvent,
PublicSignupTokenUpdatedEvent, PublicSignupTokenUpdatedEvent,
@ -18,16 +17,17 @@ import UserService from './user-service';
import { IUser } from '../types/user'; import { IUser } from '../types/user';
import { URL } from 'url'; import { URL } from 'url';
import { add } from 'date-fns'; import { add } from 'date-fns';
import EventService from './event-service';
export class PublicSignupTokenService { export class PublicSignupTokenService {
private store: IPublicSignupTokenStore; private store: IPublicSignupTokenStore;
private roleStore: IRoleStore; private roleStore: IRoleStore;
private eventStore: IEventStore;
private userService: UserService; private userService: UserService;
private eventService: EventService;
private logger: Logger; private logger: Logger;
private timer: NodeJS.Timeout; private timer: NodeJS.Timeout;
@ -38,18 +38,15 @@ export class PublicSignupTokenService {
{ {
publicSignupTokenStore, publicSignupTokenStore,
roleStore, roleStore,
eventStore, }: Pick<IUnleashStores, 'publicSignupTokenStore' | 'roleStore'>,
}: Pick<
IUnleashStores,
'publicSignupTokenStore' | 'roleStore' | 'eventStore'
>,
config: Pick<IUnleashConfig, 'getLogger' | 'authentication' | 'server'>, config: Pick<IUnleashConfig, 'getLogger' | 'authentication' | 'server'>,
userService: UserService, userService: UserService,
eventService: EventService,
) { ) {
this.store = publicSignupTokenStore; this.store = publicSignupTokenStore;
this.userService = userService; this.userService = userService;
this.eventService = eventService;
this.roleStore = roleStore; this.roleStore = roleStore;
this.eventStore = eventStore;
this.logger = config.getLogger( this.logger = config.getLogger(
'/services/public-signup-token-service.ts', '/services/public-signup-token-service.ts',
); );
@ -84,7 +81,7 @@ export class PublicSignupTokenService {
createdBy: string, createdBy: string,
): Promise<PublicSignupTokenSchema> { ): Promise<PublicSignupTokenSchema> {
const result = await this.store.update(secret, { expiresAt, enabled }); const result = await this.store.update(secret, { expiresAt, enabled });
await this.eventStore.store( await this.eventService.storeEvent(
new PublicSignupTokenUpdatedEvent({ new PublicSignupTokenUpdatedEvent({
createdBy, createdBy,
data: { secret, enabled, expiresAt }, data: { secret, enabled, expiresAt },
@ -103,7 +100,7 @@ export class PublicSignupTokenService {
rootRole: token.role.id, rootRole: token.role.id,
}); });
await this.store.addTokenUser(secret, user.id); await this.store.addTokenUser(secret, user.id);
await this.eventStore.store( await this.eventService.storeEvent(
new PublicSignupTokenUserAddedEvent({ new PublicSignupTokenUserAddedEvent({
createdBy: 'System', createdBy: 'System',
data: { secret, userId: user.id }, data: { secret, userId: user.id },
@ -133,7 +130,7 @@ export class PublicSignupTokenService {
}; };
const token = await this.store.insert(newToken); const token = await this.store.insert(newToken);
await this.eventStore.store( await this.eventService.storeEvent(
new PublicSignupTokenCreatedEvent({ new PublicSignupTokenCreatedEvent({
createdBy: createdBy, createdBy: createdBy,
data: token, data: token,

View File

@ -1,5 +1,4 @@
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { IEventStore } from '../types/stores/event-store';
import { import {
IClientSegment, IClientSegment,
IFlagResolver, IFlagResolver,
@ -23,6 +22,7 @@ import { ISegmentService } from '../segments/segment-service-interface';
import { PermissionError } from '../error'; import { PermissionError } from '../error';
import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model'; import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import EventService from './event-service';
export class SegmentService implements ISegmentService { export class SegmentService implements ISegmentService {
private logger: Logger; private logger: Logger;
@ -31,32 +31,29 @@ export class SegmentService implements ISegmentService {
private featureStrategiesStore: IFeatureStrategiesStore; private featureStrategiesStore: IFeatureStrategiesStore;
private eventStore: IEventStore;
private changeRequestAccessReadModel: IChangeRequestAccessReadModel; private changeRequestAccessReadModel: IChangeRequestAccessReadModel;
private config: IUnleashConfig; private config: IUnleashConfig;
private flagResolver: IFlagResolver; private flagResolver: IFlagResolver;
private eventService: EventService;
private privateProjectChecker: IPrivateProjectChecker; private privateProjectChecker: IPrivateProjectChecker;
constructor( constructor(
{ {
segmentStore, segmentStore,
featureStrategiesStore, featureStrategiesStore,
eventStore, }: Pick<IUnleashStores, 'segmentStore' | 'featureStrategiesStore'>,
}: Pick<
IUnleashStores,
'segmentStore' | 'featureStrategiesStore' | 'eventStore'
>,
changeRequestAccessReadModel: IChangeRequestAccessReadModel, changeRequestAccessReadModel: IChangeRequestAccessReadModel,
config: IUnleashConfig, config: IUnleashConfig,
eventService: EventService,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
) { ) {
this.segmentStore = segmentStore; this.segmentStore = segmentStore;
this.featureStrategiesStore = featureStrategiesStore; this.featureStrategiesStore = featureStrategiesStore;
this.eventStore = eventStore; this.eventService = eventService;
this.changeRequestAccessReadModel = changeRequestAccessReadModel; this.changeRequestAccessReadModel = changeRequestAccessReadModel;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.logger = config.getLogger('services/segment-service.ts'); this.logger = config.getLogger('services/segment-service.ts');
@ -117,7 +114,7 @@ export class SegmentService implements ISegmentService {
await this.validateName(input.name); await this.validateName(input.name);
const segment = await this.segmentStore.create(input, user); const segment = await this.segmentStore.create(input, user);
await this.eventStore.store({ await this.eventService.storeEvent({
type: SEGMENT_CREATED, type: SEGMENT_CREATED,
createdBy: user.email || user.username || 'unknown', createdBy: user.email || user.username || 'unknown',
data: segment, data: segment,
@ -149,7 +146,7 @@ export class SegmentService implements ISegmentService {
const segment = await this.segmentStore.update(id, input); const segment = await this.segmentStore.update(id, input);
await this.eventStore.store({ await this.eventService.storeEvent({
type: SEGMENT_UPDATED, type: SEGMENT_UPDATED,
createdBy: user.email || user.username || 'unknown', createdBy: user.email || user.username || 'unknown',
data: segment, data: segment,
@ -161,7 +158,7 @@ export class SegmentService implements ISegmentService {
const segment = await this.segmentStore.get(id); const segment = await this.segmentStore.get(id);
await this.stopWhenChangeRequestsEnabled(segment.project, user); await this.stopWhenChangeRequestsEnabled(segment.project, user);
await this.segmentStore.delete(id); await this.segmentStore.delete(id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: SEGMENT_DELETED, type: SEGMENT_DELETED,
createdBy: user.email || user.username, createdBy: user.email || user.username,
data: segment, data: segment,
@ -171,7 +168,7 @@ export class SegmentService implements ISegmentService {
async unprotectedDelete(id: number, user: User): Promise<void> { async unprotectedDelete(id: number, user: User): Promise<void> {
const segment = await this.segmentStore.get(id); const segment = await this.segmentStore.get(id);
await this.segmentStore.delete(id); await this.segmentStore.delete(id);
await this.eventStore.store({ await this.eventService.storeEvent({
type: SEGMENT_DELETED, type: SEGMENT_DELETED,
createdBy: user.email || user.username, createdBy: user.email || user.username,
data: segment, data: segment,

View File

@ -2,12 +2,12 @@ import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { ISettingStore } from '../types/stores/settings-store'; import { ISettingStore } from '../types/stores/settings-store';
import { IEventStore } from '../types/stores/event-store';
import { import {
SettingCreatedEvent, SettingCreatedEvent,
SettingDeletedEvent, SettingDeletedEvent,
SettingUpdatedEvent, SettingUpdatedEvent,
} from '../types/events'; } from '../types/events';
import EventService from './event-service';
export default class SettingService { export default class SettingService {
private config: IUnleashConfig; private config: IUnleashConfig;
@ -16,19 +16,17 @@ export default class SettingService {
private settingStore: ISettingStore; private settingStore: ISettingStore;
private eventStore: IEventStore; private eventService: EventService;
constructor( constructor(
{ { settingStore }: Pick<IUnleashStores, 'settingStore'>,
settingStore,
eventStore,
}: Pick<IUnleashStores, 'settingStore' | 'eventStore'>,
config: IUnleashConfig, config: IUnleashConfig,
eventService: EventService,
) { ) {
this.config = config; this.config = config;
this.logger = config.getLogger('services/setting-service.ts'); this.logger = config.getLogger('services/setting-service.ts');
this.settingStore = settingStore; this.settingStore = settingStore;
this.eventStore = eventStore; this.eventService = eventService;
} }
async get<T>(id: string, defaultValue?: T): Promise<T> { async get<T>(id: string, defaultValue?: T): Promise<T> {
@ -40,7 +38,7 @@ export default class SettingService {
const exists = await this.settingStore.exists(id); const exists = await this.settingStore.exists(id);
if (exists) { if (exists) {
await this.settingStore.updateRow(id, value); await this.settingStore.updateRow(id, value);
await this.eventStore.store( await this.eventService.storeEvent(
new SettingUpdatedEvent({ new SettingUpdatedEvent({
createdBy, createdBy,
data: { id }, data: { id },
@ -48,7 +46,7 @@ export default class SettingService {
); );
} else { } else {
await this.settingStore.insert(id, value); await this.settingStore.insert(id, value);
await this.eventStore.store( await this.eventService.storeEvent(
new SettingCreatedEvent({ new SettingCreatedEvent({
createdBy, createdBy,
data: { id }, data: { id },
@ -59,7 +57,7 @@ export default class SettingService {
async delete(id: string, createdBy: string): Promise<void> { async delete(id: string, createdBy: string): Promise<void> {
await this.settingStore.delete(id); await this.settingStore.delete(id);
await this.eventStore.store( await this.eventService.storeEvent(
new SettingDeletedEvent({ new SettingDeletedEvent({
createdBy, createdBy,
data: { data: {

View File

@ -13,14 +13,20 @@ import {
} from '../types/events'; } from '../types/events';
import { GLOBAL_ENV } from '../types/environment'; import { GLOBAL_ENV } from '../types/environment';
import variantsExportV3 from '../../test/examples/variantsexport_v3.json'; import variantsExportV3 from '../../test/examples/variantsexport_v3.json';
import EventService from './event-service';
const oldExportExample = require('./state-service-export-v1.json'); const oldExportExample = require('./state-service-export-v1.json');
function getSetup() { function getSetup() {
const stores = createStores(); const stores = createStores();
const eventService = new EventService(stores, { getLogger });
return { return {
stateService: new StateService(stores, { stateService: new StateService(
getLogger, stores,
}), {
getLogger,
},
eventService,
),
stores, stores,
}; };
} }
@ -61,10 +67,15 @@ async function setupV3VariantsCompatibilityScenario(
], ],
); );
}); });
const eventService = new EventService(stores, { getLogger });
return { return {
stateService: new StateService(stores, { stateService: new StateService(
getLogger, stores,
}), {
getLogger,
},
eventService,
),
stores, stores,
}; };
} }
@ -576,9 +587,14 @@ test('Should export projects', async () => {
test('exporting to new format works', async () => { test('exporting to new format works', async () => {
const stores = createStores(); const stores = createStores();
const stateService = new StateService(stores, { const eventService = new EventService(stores, { getLogger });
getLogger, const stateService = new StateService(
}); stores,
{
getLogger,
},
eventService,
);
await stores.projectStore.create({ await stores.projectStore.create({
id: 'fancy', id: 'fancy',
name: 'extra', name: 'extra',

View File

@ -39,7 +39,6 @@ import {
import { IProjectStore } from '../types/stores/project-store'; import { IProjectStore } from '../types/stores/project-store';
import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store'; import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store';
import { ITagStore } from '../types/stores/tag-store'; import { ITagStore } from '../types/stores/tag-store';
import { IEventStore } from '../types/stores/event-store';
import { IStrategy, IStrategyStore } from '../types/stores/strategy-store'; import { IStrategy, IStrategyStore } from '../types/stores/strategy-store';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store'; import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
@ -50,6 +49,7 @@ import { DEFAULT_ENV } from '../util/constants';
import { GLOBAL_ENV } from '../types/environment'; import { GLOBAL_ENV } from '../types/environment';
import { ISegmentStore } from '../types/stores/segment-store'; import { ISegmentStore } from '../types/stores/segment-store';
import { PartialSome } from '../types/partial'; import { PartialSome } from '../types/partial';
import EventService from './event-service';
export interface IBackupOption { export interface IBackupOption {
includeFeatureToggles: boolean; includeFeatureToggles: boolean;
@ -76,7 +76,7 @@ export default class StateService {
private strategyStore: IStrategyStore; private strategyStore: IStrategyStore;
private eventStore: IEventStore; private eventService: EventService;
private tagStore: ITagStore; private tagStore: ITagStore;
@ -95,8 +95,9 @@ export default class StateService {
constructor( constructor(
stores: IUnleashStores, stores: IUnleashStores,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.eventStore = stores.eventStore; this.eventService = eventService;
this.toggleStore = stores.featureToggleStore; this.toggleStore = stores.featureToggleStore;
this.strategyStore = stores.strategyStore; this.strategyStore = stores.strategyStore;
this.tagStore = stores.tagStore; this.tagStore = stores.tagStore;
@ -369,7 +370,7 @@ export default class StateService {
if (dropBeforeImport) { if (dropBeforeImport) {
this.logger.info('Dropping existing feature toggles'); this.logger.info('Dropping existing feature toggles');
await this.toggleStore.deleteAll(); await this.toggleStore.deleteAll();
await this.eventStore.store({ await this.eventService.storeEvent({
type: DROP_FEATURES, type: DROP_FEATURES,
createdBy: userName, createdBy: userName,
data: { name: 'all-features' }, data: { name: 'all-features' },
@ -387,7 +388,7 @@ export default class StateService {
feature.project, feature.project,
this.enabledIn(feature.name, featureEnvironments), this.enabledIn(feature.name, featureEnvironments),
); );
await this.eventStore.store({ await this.eventService.storeEvent({
type: FEATURE_IMPORT, type: FEATURE_IMPORT,
createdBy: userName, createdBy: userName,
data: feature, data: feature,
@ -411,7 +412,7 @@ export default class StateService {
if (dropBeforeImport) { if (dropBeforeImport) {
this.logger.info('Dropping existing strategies'); this.logger.info('Dropping existing strategies');
await this.strategyStore.dropCustomStrategies(); await this.strategyStore.dropCustomStrategies();
await this.eventStore.store({ await this.eventService.storeEvent({
type: DROP_STRATEGIES, type: DROP_STRATEGIES,
createdBy: userName, createdBy: userName,
data: { name: 'all-strategies' }, data: { name: 'all-strategies' },
@ -424,7 +425,7 @@ export default class StateService {
.filter(filterEqual(oldStrategies)) .filter(filterEqual(oldStrategies))
.map((strategy) => .map((strategy) =>
this.strategyStore.importStrategy(strategy).then(() => { this.strategyStore.importStrategy(strategy).then(() => {
this.eventStore.store({ this.eventService.storeEvent({
type: STRATEGY_IMPORT, type: STRATEGY_IMPORT,
createdBy: userName, createdBy: userName,
data: strategy, data: strategy,
@ -448,7 +449,7 @@ export default class StateService {
if (dropBeforeImport) { if (dropBeforeImport) {
this.logger.info('Dropping existing environments'); this.logger.info('Dropping existing environments');
await this.environmentStore.deleteAll(); await this.environmentStore.deleteAll();
await this.eventStore.store({ await this.eventService.storeEvent({
type: DROP_ENVIRONMENTS, type: DROP_ENVIRONMENTS,
createdBy: userName, createdBy: userName,
data: { name: 'all-environments' }, data: { name: 'all-environments' },
@ -467,7 +468,7 @@ export default class StateService {
createdBy: userName, createdBy: userName,
data: env, data: env,
})); }));
await this.eventStore.batchStore(importedEnvironmentEvents); await this.eventService.storeEvents(importedEnvironmentEvents);
} }
return importedEnvs; return importedEnvs;
} }
@ -487,7 +488,7 @@ export default class StateService {
if (dropBeforeImport) { if (dropBeforeImport) {
this.logger.info('Dropping existing projects'); this.logger.info('Dropping existing projects');
await this.projectStore.deleteAll(); await this.projectStore.deleteAll();
await this.eventStore.store({ await this.eventService.storeEvent({
type: DROP_PROJECTS, type: DROP_PROJECTS,
createdBy: userName, createdBy: userName,
data: { name: 'all-projects' }, data: { name: 'all-projects' },
@ -508,7 +509,7 @@ export default class StateService {
createdBy: userName, createdBy: userName,
data: project, data: project,
})); }));
await this.eventStore.batchStore(importedProjectEvents); await this.eventService.storeEvents(importedProjectEvents);
} }
} }
@ -538,7 +539,7 @@ export default class StateService {
await this.featureTagStore.deleteAll(); await this.featureTagStore.deleteAll();
await this.tagStore.deleteAll(); await this.tagStore.deleteAll();
await this.tagTypeStore.deleteAll(); await this.tagTypeStore.deleteAll();
await this.eventStore.batchStore([ await this.eventService.storeEvents([
{ {
type: DROP_FEATURE_TAGS, type: DROP_FEATURE_TAGS,
createdBy: userName, createdBy: userName,
@ -601,7 +602,7 @@ export default class StateService {
createdBy: userName, createdBy: userName,
data: tag, data: tag,
})); }));
await this.eventStore.batchStore(importedFeatureTagEvents); await this.eventService.storeEvents(importedFeatureTagEvents);
} }
} }
@ -626,7 +627,7 @@ export default class StateService {
createdBy: userName, createdBy: userName,
data: tag, data: tag,
})); }));
await this.eventStore.batchStore(importedTagEvents); await this.eventService.storeEvents(importedTagEvents);
} }
} }
@ -650,7 +651,7 @@ export default class StateService {
createdBy: userName, createdBy: userName,
data: tagType, data: tagType,
})); }));
await this.eventStore.batchStore(importedTagTypeEvents); await this.eventService.storeEvents(importedTagTypeEvents);
} }
} }

View File

@ -1,13 +1,13 @@
import { Logger } from '../logger'; import { Logger } from '../logger';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { IEventStore } from '../types/stores/event-store';
import { import {
IMinimalStrategy, IMinimalStrategy,
IStrategy, IStrategy,
IStrategyStore, IStrategyStore,
} from '../types/stores/strategy-store'; } from '../types/stores/strategy-store';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
import EventService from './event-service';
const strategySchema = require('./strategy-schema'); const strategySchema = require('./strategy-schema');
const NameExistsError = require('../error/name-exists-error'); const NameExistsError = require('../error/name-exists-error');
@ -24,17 +24,15 @@ class StrategyService {
private strategyStore: IStrategyStore; private strategyStore: IStrategyStore;
private eventStore: IEventStore; private eventService: EventService;
constructor( constructor(
{ { strategyStore }: Pick<IUnleashStores, 'strategyStore'>,
strategyStore,
eventStore,
}: Pick<IUnleashStores, 'strategyStore' | 'eventStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.strategyStore = strategyStore; this.strategyStore = strategyStore;
this.eventStore = eventStore; this.eventService = eventService;
this.logger = getLogger('services/strategy-service.js'); this.logger = getLogger('services/strategy-service.js');
} }
@ -53,7 +51,7 @@ class StrategyService {
const strategy = await this.strategyStore.get(strategyName); const strategy = await this.strategyStore.get(strategyName);
await this._validateEditable(strategy); await this._validateEditable(strategy);
await this.strategyStore.delete(strategyName); await this.strategyStore.delete(strategyName);
await this.eventStore.store({ await this.eventService.storeEvent({
type: STRATEGY_DELETED, type: STRATEGY_DELETED,
createdBy: userName, createdBy: userName,
data: { data: {
@ -69,7 +67,7 @@ class StrategyService {
if (await this.strategyStore.exists(strategyName)) { if (await this.strategyStore.exists(strategyName)) {
// Check existence // Check existence
await this.strategyStore.deprecateStrategy({ name: strategyName }); await this.strategyStore.deprecateStrategy({ name: strategyName });
await this.eventStore.store({ await this.eventService.storeEvent({
type: STRATEGY_DEPRECATED, type: STRATEGY_DEPRECATED,
createdBy: userName, createdBy: userName,
data: { data: {
@ -89,7 +87,7 @@ class StrategyService {
): Promise<void> { ): Promise<void> {
await this.strategyStore.get(strategyName); // Check existence await this.strategyStore.get(strategyName); // Check existence
await this.strategyStore.reactivateStrategy({ name: strategyName }); await this.strategyStore.reactivateStrategy({ name: strategyName });
await this.eventStore.store({ await this.eventService.storeEvent({
type: STRATEGY_REACTIVATED, type: STRATEGY_REACTIVATED,
createdBy: userName, createdBy: userName,
data: { data: {
@ -106,7 +104,7 @@ class StrategyService {
strategy.deprecated = false; strategy.deprecated = false;
await this._validateStrategyName(strategy); await this._validateStrategyName(strategy);
await this.strategyStore.createStrategy(strategy); await this.strategyStore.createStrategy(strategy);
await this.eventStore.store({ await this.eventService.storeEvent({
type: STRATEGY_CREATED, type: STRATEGY_CREATED,
createdBy: userName, createdBy: userName,
data: strategy, data: strategy,
@ -122,7 +120,7 @@ class StrategyService {
const strategy = await this.strategyStore.get(input.name); const strategy = await this.strategyStore.get(input.name);
await this._validateEditable(strategy); await this._validateEditable(strategy);
await this.strategyStore.updateStrategy(value); await this.strategyStore.updateStrategy(value);
await this.eventStore.store({ await this.eventService.storeEvent({
type: STRATEGY_UPDATED, type: STRATEGY_UPDATED,
createdBy: userName, createdBy: userName,
data: value, data: value,

View File

@ -5,25 +5,23 @@ import { Logger } from '../logger';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import { ITagStore } from '../types/stores/tag-store'; import { ITagStore } from '../types/stores/tag-store';
import { IEventStore } from '../types/stores/event-store';
import { ITag } from '../types/model'; import { ITag } from '../types/model';
import EventService from './event-service';
export default class TagService { export default class TagService {
private tagStore: ITagStore; private tagStore: ITagStore;
private eventStore: IEventStore; private eventService: EventService;
private logger: Logger; private logger: Logger;
constructor( constructor(
{ { tagStore }: Pick<IUnleashStores, 'tagStore'>,
tagStore,
eventStore,
}: Pick<IUnleashStores, 'tagStore' | 'eventStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.tagStore = tagStore; this.tagStore = tagStore;
this.eventStore = eventStore; this.eventService = eventService;
this.logger = getLogger('services/tag-service.js'); this.logger = getLogger('services/tag-service.js');
} }
@ -55,7 +53,7 @@ export default class TagService {
async createTag(tag: ITag, userName: string): Promise<ITag> { async createTag(tag: ITag, userName: string): Promise<ITag> {
const data = await this.validate(tag); const data = await this.validate(tag);
await this.tagStore.createTag(data); await this.tagStore.createTag(data);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_CREATED, type: TAG_CREATED,
createdBy: userName, createdBy: userName,
data, data,
@ -66,7 +64,7 @@ export default class TagService {
async deleteTag(tag: ITag, userName: string): Promise<void> { async deleteTag(tag: ITag, userName: string): Promise<void> {
await this.tagStore.delete(tag); await this.tagStore.delete(tag);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_DELETED, type: TAG_DELETED,
createdBy: userName, createdBy: userName,
data: tag, data: tag,

View File

@ -11,25 +11,23 @@ import {
import { Logger } from '../logger'; import { Logger } from '../logger';
import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store'; import { ITagType, ITagTypeStore } from '../types/stores/tag-type-store';
import { IEventStore } from '../types/stores/event-store';
import { IUnleashConfig } from '../types/option'; import { IUnleashConfig } from '../types/option';
import EventService from './event-service';
export default class TagTypeService { export default class TagTypeService {
private tagTypeStore: ITagTypeStore; private tagTypeStore: ITagTypeStore;
private eventStore: IEventStore; private eventService: EventService;
private logger: Logger; private logger: Logger;
constructor( constructor(
{ { tagTypeStore }: Pick<IUnleashStores, 'tagTypeStore'>,
tagTypeStore,
eventStore,
}: Pick<IUnleashStores, 'tagTypeStore' | 'eventStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
eventService: EventService,
) { ) {
this.tagTypeStore = tagTypeStore; this.tagTypeStore = tagTypeStore;
this.eventStore = eventStore; this.eventService = eventService;
this.logger = getLogger('services/tag-type-service.js'); this.logger = getLogger('services/tag-type-service.js');
} }
@ -50,7 +48,7 @@ export default class TagTypeService {
)) as ITagType; )) as ITagType;
await this.validateUnique(data.name); await this.validateUnique(data.name);
await this.tagTypeStore.createTagType(data); await this.tagTypeStore.createTagType(data);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_TYPE_CREATED, type: TAG_TYPE_CREATED,
createdBy: userName || 'unleash-system', createdBy: userName || 'unleash-system',
data, data,
@ -77,7 +75,7 @@ export default class TagTypeService {
async deleteTagType(name: string, userName: string): Promise<void> { async deleteTagType(name: string, userName: string): Promise<void> {
await this.tagTypeStore.delete(name); await this.tagTypeStore.delete(name);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_TYPE_DELETED, type: TAG_TYPE_DELETED,
createdBy: userName || 'unleash-system', createdBy: userName || 'unleash-system',
data: { name }, data: { name },
@ -90,7 +88,7 @@ export default class TagTypeService {
): Promise<ITagType> { ): Promise<ITagType> {
const data = await tagTypeSchema.validateAsync(updatedTagType); const data = await tagTypeSchema.validateAsync(updatedTagType);
await this.tagTypeStore.updateTagType(data); await this.tagTypeStore.updateTagType(data);
await this.eventStore.store({ await this.eventService.storeEvent({
type: TAG_TYPE_UPDATED, type: TAG_TYPE_UPDATED,
createdBy: userName || 'unleash-system', createdBy: userName || 'unleash-system',
data, data,

View File

@ -14,7 +14,8 @@ import User from '../types/user';
import FakeResetTokenStore from '../../test/fixtures/fake-reset-token-store'; import FakeResetTokenStore from '../../test/fixtures/fake-reset-token-store';
import SettingService from './setting-service'; import SettingService from './setting-service';
import FakeSettingStore from '../../test/fixtures/fake-setting-store'; import FakeSettingStore from '../../test/fixtures/fake-setting-store';
import FakeEventStore from '../../test/fixtures/fake-event-store'; import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
@ -32,18 +33,23 @@ test('Should create new user', async () => {
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -75,18 +81,23 @@ test('Should create default user', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -110,18 +121,23 @@ test('Should be a valid password', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -143,18 +159,23 @@ test('Password must be at least 10 chars', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -178,18 +199,23 @@ test('The password must contain at least one uppercase letter.', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -215,18 +241,23 @@ test('The password must contain at least one number', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -251,18 +282,23 @@ test('The password must contain at least one special character', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -287,18 +323,23 @@ test('Should be a valid password with special chars', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -320,18 +361,23 @@ test('Should send password reset email if user exists', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });
@ -369,18 +415,23 @@ test('Should throttle password reset email', async () => {
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new FakeSessionStore(); const sessionStore = new FakeSessionStore();
const sessionService = new SessionService({ sessionStore }, config); const sessionService = new SessionService({ sessionStore }, config);
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
const service = new UserService({ userStore, eventStore }, config, { const service = new UserService({ userStore }, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });

View File

@ -17,7 +17,6 @@ import SessionService from './session-service';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import PasswordUndefinedError from '../error/password-undefined'; import PasswordUndefinedError from '../error/password-undefined';
import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events'; import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events';
import { IEventStore } from '../types/stores/event-store';
import { IUserStore } from '../types/stores/user-store'; import { IUserStore } from '../types/stores/user-store';
import { RoleName } from '../types/model'; import { RoleName } from '../types/model';
import SettingService from './setting-service'; import SettingService from './setting-service';
@ -28,6 +27,7 @@ import BadDataError from '../error/bad-data-error';
import { isDefined } from '../util/isDefined'; import { isDefined } from '../util/isDefined';
import { TokenUserSchema } from '../openapi/spec/token-user-schema'; import { TokenUserSchema } from '../openapi/spec/token-user-schema';
import PasswordMismatch from '../error/password-mismatch'; import PasswordMismatch from '../error/password-mismatch';
import EventService from './event-service';
const systemUser = new User({ id: -1, username: 'system' }); const systemUser = new User({ id: -1, username: 'system' });
@ -64,7 +64,7 @@ class UserService {
private store: IUserStore; private store: IUserStore;
private eventStore: IEventStore; private eventService: EventService;
private accessService: AccessService; private accessService: AccessService;
@ -81,7 +81,7 @@ class UserService {
private baseUriPath: string; private baseUriPath: string;
constructor( constructor(
stores: Pick<IUnleashStores, 'userStore' | 'eventStore'>, stores: Pick<IUnleashStores, 'userStore'>,
{ {
server, server,
getLogger, getLogger,
@ -91,13 +91,14 @@ class UserService {
accessService: AccessService; accessService: AccessService;
resetTokenService: ResetTokenService; resetTokenService: ResetTokenService;
emailService: EmailService; emailService: EmailService;
eventService: EventService;
sessionService: SessionService; sessionService: SessionService;
settingService: SettingService; settingService: SettingService;
}, },
) { ) {
this.logger = getLogger('service/user-service.js'); this.logger = getLogger('service/user-service.js');
this.store = stores.userStore; this.store = stores.userStore;
this.eventStore = stores.eventStore; this.eventService = services.eventService;
this.accessService = services.accessService; this.accessService = services.accessService;
this.resetTokenService = services.resetTokenService; this.resetTokenService = services.resetTokenService;
this.emailService = services.emailService; this.emailService = services.emailService;
@ -208,7 +209,7 @@ class UserService {
await this.store.setPasswordHash(user.id, passwordHash); await this.store.setPasswordHash(user.id, passwordHash);
} }
await this.eventStore.store({ await this.eventService.storeEvent({
type: USER_CREATED, type: USER_CREATED,
createdBy: this.getCreatedBy(updatedBy), createdBy: this.getCreatedBy(updatedBy),
data: this.mapUserToData(user), data: this.mapUserToData(user),
@ -257,7 +258,7 @@ class UserService {
? await this.store.update(id, payload) ? await this.store.update(id, payload)
: preUser; : preUser;
await this.eventStore.store({ await this.eventService.storeEvent({
type: USER_UPDATED, type: USER_UPDATED,
createdBy: this.getCreatedBy(updatedBy), createdBy: this.getCreatedBy(updatedBy),
data: this.mapUserToData(user), data: this.mapUserToData(user),
@ -274,7 +275,7 @@ class UserService {
await this.store.delete(userId); await this.store.delete(userId);
await this.eventStore.store({ await this.eventService.storeEvent({
type: USER_DELETED, type: USER_DELETED,
createdBy: this.getCreatedBy(updatedBy), createdBy: this.getCreatedBy(updatedBy),
preData: this.mapUserToData(user), preData: this.mapUserToData(user),

View File

@ -278,22 +278,15 @@ class BaseEvent implements IBaseEvent {
readonly createdBy: string; readonly createdBy: string;
readonly tags: ITag[];
/** /**
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization * @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
*/ */
constructor( constructor(type: IEventType, createdBy: string | IUser) {
type: IEventType,
createdBy: string | IUser,
tags: ITag[] = [],
) {
this.type = type; this.type = type;
this.createdBy = this.createdBy =
typeof createdBy === 'string' typeof createdBy === 'string'
? createdBy ? createdBy
: extractUsernameFromUser(createdBy); : extractUsernameFromUser(createdBy);
this.tags = tags;
} }
} }
@ -310,13 +303,8 @@ export class FeatureStaleEvent extends BaseEvent {
project: string; project: string;
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
}) { }) {
super( super(p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF, p.createdBy);
p.stale ? FEATURE_STALE_ON : FEATURE_STALE_OFF,
p.createdBy,
p.tags,
);
this.project = p.project; this.project = p.project;
this.featureName = p.featureName; this.featureName = p.featureName;
} }
@ -338,14 +326,12 @@ export class FeatureEnvironmentEvent extends BaseEvent {
featureName: string; featureName: string;
environment: string; environment: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
}) { }) {
super( super(
p.enabled p.enabled
? FEATURE_ENVIRONMENT_ENABLED ? FEATURE_ENVIRONMENT_ENABLED
: FEATURE_ENVIRONMENT_DISABLED, : FEATURE_ENVIRONMENT_DISABLED,
p.createdBy, p.createdBy,
p.tags,
); );
this.project = p.project; this.project = p.project;
this.featureName = p.featureName; this.featureName = p.featureName;
@ -374,9 +360,8 @@ export class StrategiesOrderChangedEvent extends BaseEvent {
createdBy: string | IUser; createdBy: string | IUser;
data: StrategyIds; data: StrategyIds;
preData: StrategyIds; preData: StrategyIds;
tags: ITag[];
}) { }) {
super(STRATEGY_ORDER_CHANGED, p.createdBy, p.tags); super(STRATEGY_ORDER_CHANGED, p.createdBy);
const { project, featureName, environment, data, preData } = p; const { project, featureName, environment, data, preData } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -402,11 +387,10 @@ export class FeatureVariantEvent extends BaseEvent {
project: string; project: string;
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
newVariants: IVariant[]; newVariants: IVariant[];
oldVariants: IVariant[]; oldVariants: IVariant[];
}) { }) {
super(FEATURE_VARIANTS_UPDATED, p.createdBy, p.tags); super(FEATURE_VARIANTS_UPDATED, p.createdBy);
this.project = p.project; this.project = p.project;
this.featureName = p.featureName; this.featureName = p.featureName;
this.data = { variants: p.newVariants }; this.data = { variants: p.newVariants };
@ -433,11 +417,10 @@ export class EnvironmentVariantEvent extends BaseEvent {
environment: string; environment: string;
project: string; project: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
newVariants: IVariant[]; newVariants: IVariant[];
oldVariants: IVariant[]; oldVariants: IVariant[];
}) { }) {
super(FEATURE_ENVIRONMENT_VARIANTS_UPDATED, p.createdBy, p.tags); super(FEATURE_ENVIRONMENT_VARIANTS_UPDATED, p.createdBy);
this.featureName = p.featureName; this.featureName = p.featureName;
this.environment = p.environment; this.environment = p.environment;
this.project = p.project; this.project = p.project;
@ -464,9 +447,8 @@ export class FeatureChangeProjectEvent extends BaseEvent {
newProject: string; newProject: string;
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
}) { }) {
super(FEATURE_PROJECT_CHANGE, p.createdBy, p.tags); super(FEATURE_PROJECT_CHANGE, p.createdBy);
const { newProject, oldProject, featureName } = p; const { newProject, oldProject, featureName } = p;
this.project = newProject; this.project = newProject;
this.featureName = featureName; this.featureName = featureName;
@ -489,9 +471,8 @@ export class FeatureCreatedEvent extends BaseEvent {
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
data: FeatureToggle; data: FeatureToggle;
tags: ITag[];
}) { }) {
super(FEATURE_CREATED, p.createdBy, p.tags); super(FEATURE_CREATED, p.createdBy);
const { project, featureName, data } = p; const { project, featureName, data } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -511,9 +492,8 @@ export class FeatureArchivedEvent extends BaseEvent {
project: string; project: string;
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
}) { }) {
super(FEATURE_ARCHIVED, p.createdBy, p.tags); super(FEATURE_ARCHIVED, p.createdBy);
const { project, featureName } = p; const { project, featureName } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -532,9 +512,8 @@ export class FeatureRevivedEvent extends BaseEvent {
project: string; project: string;
featureName: string; featureName: string;
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[];
}) { }) {
super(FEATURE_REVIVED, p.createdBy, p.tags); super(FEATURE_REVIVED, p.createdBy);
const { project, featureName } = p; const { project, featureName } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -548,6 +527,8 @@ export class FeatureDeletedEvent extends BaseEvent {
readonly preData: FeatureToggle; readonly preData: FeatureToggle;
readonly tags: ITag[];
/** /**
* @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization * @param createdBy accepts a string for backward compatibility. Prefer using IUser for standardization
*/ */
@ -558,11 +539,12 @@ export class FeatureDeletedEvent extends BaseEvent {
createdBy: string | IUser; createdBy: string | IUser;
tags: ITag[]; tags: ITag[];
}) { }) {
super(FEATURE_DELETED, p.createdBy, p.tags); super(FEATURE_DELETED, p.createdBy);
const { project, featureName, preData } = p; const { project, featureName, preData } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
this.preData = preData; this.preData = preData;
this.tags = p.tags;
} }
} }
@ -584,9 +566,8 @@ export class FeatureMetadataUpdateEvent extends BaseEvent {
project: string; project: string;
data: FeatureToggle; data: FeatureToggle;
preData: FeatureToggle; preData: FeatureToggle;
tags: ITag[];
}) { }) {
super(FEATURE_METADATA_UPDATED, p.createdBy, p.tags); super(FEATURE_METADATA_UPDATED, p.createdBy);
const { project, featureName, data, preData } = p; const { project, featureName, data, preData } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -613,9 +594,8 @@ export class FeatureStrategyAddEvent extends BaseEvent {
environment: string; environment: string;
createdBy: string | IUser; createdBy: string | IUser;
data: IStrategyConfig; data: IStrategyConfig;
tags: ITag[];
}) { }) {
super(FEATURE_STRATEGY_ADD, p.createdBy, p.tags); super(FEATURE_STRATEGY_ADD, p.createdBy);
const { project, featureName, environment, data } = p; const { project, featureName, environment, data } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -645,9 +625,8 @@ export class FeatureStrategyUpdateEvent extends BaseEvent {
createdBy: string | IUser; createdBy: string | IUser;
data: IStrategyConfig; data: IStrategyConfig;
preData: IStrategyConfig; preData: IStrategyConfig;
tags: ITag[];
}) { }) {
super(FEATURE_STRATEGY_UPDATE, p.createdBy, p.tags); super(FEATURE_STRATEGY_UPDATE, p.createdBy);
const { project, featureName, environment, data, preData } = p; const { project, featureName, environment, data, preData } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -675,9 +654,8 @@ export class FeatureStrategyRemoveEvent extends BaseEvent {
environment: string; environment: string;
createdBy: string | IUser; createdBy: string | IUser;
preData: IStrategyConfig; preData: IStrategyConfig;
tags: ITag[];
}) { }) {
super(FEATURE_STRATEGY_REMOVE, p.createdBy, p.tags); super(FEATURE_STRATEGY_REMOVE, p.createdBy);
const { project, featureName, environment, preData } = p; const { project, featureName, environment, preData } = p;
this.project = project; this.project = project;
this.featureName = featureName; this.featureName = featureName;
@ -1075,12 +1053,8 @@ export class PotentiallyStaleOnEvent extends BaseEvent {
readonly project: string; readonly project: string;
constructor(eventData: { constructor(eventData: { featureName: string; project: string }) {
featureName: string; super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system');
project: string;
tags: ITag[];
}) {
super(FEATURE_POTENTIALLY_STALE_ON, 'unleash-system', eventData.tags);
this.featureName = eventData.featureName; this.featureName = eventData.featureName;
this.project = eventData.project; this.project = eventData.project;
} }

View File

@ -5,12 +5,12 @@ import {
import dbInit, { ITestDb } from '../../helpers/database-init'; import dbInit, { ITestDb } from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger'; import getLogger from '../../../fixtures/no-logger';
import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events'; import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events';
import { IEventStore } from '../../../../lib/types/stores/event-store';
import { randomId } from '../../../../lib/util/random-id'; import { randomId } from '../../../../lib/util/random-id';
import { EventService } from '../../../../lib/services';
let app: IUnleashTest; let app: IUnleashTest;
let db: ITestDb; let db: ITestDb;
let eventStore: IEventStore; let eventService: EventService;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('event_api_serial', getLogger); db = await dbInit('event_api_serial', getLogger);
@ -21,11 +21,11 @@ beforeAll(async () => {
}, },
}, },
}); });
eventStore = db.stores.eventStore; eventService = new EventService(db.stores, { getLogger });
}); });
beforeEach(async () => { beforeEach(async () => {
await eventStore.deleteAll(); await db.stores.eventStore.deleteAll();
}); });
afterAll(async () => { afterAll(async () => {
@ -50,7 +50,7 @@ test('returns events given a name', async () => {
}); });
test('Can filter by project', async () => { test('Can filter by project', async () => {
await eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
project: 'something-else', project: 'something-else',
data: { id: 'some-other-feature' }, data: { id: 'some-other-feature' },
@ -58,7 +58,7 @@ test('Can filter by project', async () => {
createdBy: 'test-user', createdBy: 'test-user',
environment: 'test', environment: 'test',
}); });
await eventStore.store({ await eventService.storeEvent({
type: FEATURE_CREATED, type: FEATURE_CREATED,
project: 'default', project: 'default',
data: { id: 'feature' }, data: { id: 'feature' },
@ -96,7 +96,7 @@ test('can search for events', async () => {
await Promise.all( await Promise.all(
events.map((event) => { events.map((event) => {
return eventStore.store(event); return eventService.storeEvent(event);
}), }),
); );

View File

@ -16,7 +16,7 @@ import { RoleName } from '../../../../lib/types/model';
import SettingService from '../../../../lib/services/setting-service'; import SettingService from '../../../../lib/services/setting-service';
import FakeSettingStore from '../../../fixtures/fake-setting-store'; import FakeSettingStore from '../../../fixtures/fake-setting-store';
import { GroupService } from '../../../../lib/services/group-service'; import { GroupService } from '../../../../lib/services/group-service';
import FakeEventStore from '../../../fixtures/fake-event-store'; import { EventService } from '../../../../lib/services';
let app; let app;
let stores; let stores;
@ -49,7 +49,8 @@ beforeAll(async () => {
db = await dbInit('reset_password_api_serial', getLogger); db = await dbInit('reset_password_api_serial', getLogger);
stores = db.stores; stores = db.stores;
app = await setupApp(stores); app = await setupApp(stores);
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService); accessService = new AccessService(stores, config, groupService);
const emailService = new EmailService(config.email, config.getLogger); const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new SessionStore( const sessionStore = new SessionStore(
@ -61,14 +62,15 @@ beforeAll(async () => {
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
userService = new UserService(stores, config, { userService = new UserService(stores, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });

View File

@ -12,6 +12,7 @@ import { RoleName } from '../../../../lib/types/model';
import SettingService from '../../../../lib/services/setting-service'; import SettingService from '../../../../lib/services/setting-service';
import { GroupService } from '../../../../lib/services/group-service'; import { GroupService } from '../../../../lib/services/group-service';
import ResetTokenService from '../../../../lib/services/reset-token-service'; import ResetTokenService from '../../../../lib/services/reset-token-service';
import { EventService } from '../../../../lib/services';
let app; let app;
let stores; let stores;
@ -34,18 +35,20 @@ beforeEach(async () => {
db = await dbInit('simple_password_provider_api_serial', getLogger); db = await dbInit('simple_password_provider_api_serial', getLogger);
stores = db.stores; stores = db.stores;
app = await setupApp(stores); app = await setupApp(stores);
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const resetTokenService = new ResetTokenService(stores, config); const resetTokenService = new ResetTokenService(stores, config);
// @ts-ignore // @ts-ignore
const emailService = new EmailService(undefined, config.getLogger); const emailService = new EmailService(undefined, config.getLogger);
const sessionService = new SessionService(stores, config); const sessionService = new SessionService(stores, config);
const settingService = new SettingService(stores, config); const settingService = new SettingService(stores, config, eventService);
userService = new UserService(stores, config, { userService = new UserService(stores, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });

View File

@ -20,7 +20,7 @@ import { DEFAULT_PROJECT } from '../../../lib/types/project';
import { ALL_PROJECTS } from '../../../lib/util/constants'; import { ALL_PROJECTS } from '../../../lib/util/constants';
import { SegmentService } from '../../../lib/services/segment-service'; import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
@ -28,6 +28,7 @@ import { DependentFeaturesReadModel } from '../../../lib/features/dependent-feat
let db: ITestDb; let db: ITestDb;
let stores: IUnleashStores; let stores: IUnleashStores;
let accessService: AccessService; let accessService: AccessService;
let eventService: EventService;
let groupService: GroupService; let groupService: GroupService;
let featureToggleService; let featureToggleService;
let favoritesService; let favoritesService;
@ -237,7 +238,8 @@ beforeAll(async () => {
// @ts-ignore // @ts-ignore
experimental: { environments: { enabled: true } }, experimental: { environments: { enabled: true } },
}); });
groupService = new GroupService(stores, { getLogger }); eventService = new EventService(stores, config);
groupService = new GroupService(stores, { getLogger }, eventService);
accessService = new AccessService(stores, config, groupService); accessService = new AccessService(stores, config, groupService);
const roles = await accessService.getRootRoles(); const roles = await accessService.getRootRoles();
editorRole = roles.find((r) => r.name === RoleName.EDITOR); editorRole = roles.find((r) => r.name === RoleName.EDITOR);
@ -261,14 +263,16 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
), ),
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService( projectService = new ProjectService(
stores, stores,
config, config,
@ -276,6 +280,7 @@ beforeAll(async () => {
featureToggleService, featureToggleService,
groupService, groupService,
favoritesService, favoritesService,
eventService,
privateProjectChecker, privateProjectChecker,
); );

View File

@ -7,6 +7,7 @@ import { IUnleashStores } from '../../../lib/types';
import SimpleAddon from '../../../lib/services/addon-service-test-simple-addon'; import SimpleAddon from '../../../lib/services/addon-service-test-simple-addon';
import TagTypeService from '../../../lib/services/tag-type-service'; import TagTypeService from '../../../lib/services/tag-type-service';
import { FEATURE_CREATED } from '../../../lib/types/events'; import { FEATURE_CREATED } from '../../../lib/types/events';
import { EventService } from '../../../lib/services';
const addonProvider = { simple: new SimpleAddon() }; const addonProvider = { simple: new SimpleAddon() };
@ -20,11 +21,13 @@ beforeAll(async () => {
}); });
db = await dbInit('addon_service_serial', getLogger); db = await dbInit('addon_service_serial', getLogger);
stores = db.stores; stores = db.stores;
const tagTypeService = new TagTypeService(stores, config); const eventService = new EventService(stores, config);
const tagTypeService = new TagTypeService(stores, config, eventService);
addonService = new AddonService( addonService = new AddonService(
stores, stores,
config, config,
tagTypeService, tagTypeService,
eventService,
addonProvider, addonProvider,
); );
}); });

View File

@ -10,7 +10,7 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { AccessService } from '../../../lib/services/access-service'; import { AccessService } from '../../../lib/services/access-service';
import { SegmentService } from '../../../lib/services/segment-service'; import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
@ -27,7 +27,8 @@ beforeAll(async () => {
}); });
db = await dbInit('api_token_service_serial', getLogger); db = await dbInit('api_token_service_serial', getLogger);
stores = db.stores; stores = db.stores;
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase, db.rawDatabase,
@ -47,9 +48,11 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
), ),
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
@ -65,7 +68,7 @@ beforeAll(async () => {
name: 'Some Name', name: 'Some Name',
email: 'test@getunleash.io', email: 'test@getunleash.io',
}); });
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService( projectService = new ProjectService(
stores, stores,
config, config,
@ -73,12 +76,13 @@ beforeAll(async () => {
featureToggleService, featureToggleService,
groupService, groupService,
favoritesService, favoritesService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
await projectService.createProject(project, user); await projectService.createProject(project, user);
apiTokenService = new ApiTokenService(stores, config); apiTokenService = new ApiTokenService(stores, config, eventService);
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -4,6 +4,7 @@ import dbInit from '../helpers/database-init';
import { DEFAULT_ENV } from '../../../lib/util'; import { DEFAULT_ENV } from '../../../lib/util';
import { import {
AccessService, AccessService,
EventService,
GroupService, GroupService,
SegmentService, SegmentService,
} from '../../../lib/services'; } from '../../../lib/services';
@ -53,8 +54,8 @@ beforeAll(async () => {
); );
unleashConfig = config; unleashConfig = config;
stores = db.stores; stores = db.stores;
const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config); const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase, db.rawDatabase,
@ -71,6 +72,7 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
@ -79,6 +81,7 @@ beforeAll(async () => {
config, config,
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
@ -457,7 +460,8 @@ test('If change requests are enabled, cannot change variants without going via C
{ name: featureName }, { name: featureName },
'test-user', 'test-user',
); );
const groupService = new GroupService(stores, unleashConfig); const eventService = new EventService(stores, unleashConfig);
const groupService = new GroupService(stores, unleashConfig, eventService);
const accessService = new AccessService( const accessService = new AccessService(
stores, stores,
unleashConfig, unleashConfig,
@ -485,6 +489,7 @@ test('If change requests are enabled, cannot change variants without going via C
}, },
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
@ -549,7 +554,8 @@ test('If CRs are protected for any environment in the project stops bulk update
project.id, project.id,
disabledEnv.name, disabledEnv.name,
); );
const groupService = new GroupService(stores, unleashConfig); const eventService = new EventService(stores, unleashConfig);
const groupService = new GroupService(stores, unleashConfig, eventService);
const accessService = new AccessService( const accessService = new AccessService(
stores, stores,
unleashConfig, unleashConfig,
@ -577,6 +583,7 @@ test('If CRs are protected for any environment in the project stops bulk update
}, },
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,

View File

@ -3,10 +3,12 @@ import getLogger from '../../fixtures/no-logger';
import { createTestConfig } from '../../config/test-config'; import { createTestConfig } from '../../config/test-config';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import GroupStore from '../../../lib/db/group-store'; import GroupStore from '../../../lib/db/group-store';
import { EventService } from '../../../lib/services';
let stores; let stores;
let db: ITestDb; let db: ITestDb;
let eventService: EventService;
let groupService: GroupService; let groupService: GroupService;
let groupStore: GroupStore; let groupStore: GroupStore;
let user; let user;
@ -21,7 +23,8 @@ beforeAll(async () => {
const config = createTestConfig({ const config = createTestConfig({
getLogger, getLogger,
}); });
groupService = new GroupService(stores, config); eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
groupStore = stores.groupStore; groupStore = stores.groupStore;
await stores.groupStore.create({ await stores.groupStore.create({

View File

@ -27,6 +27,7 @@ import { ISegmentService } from '../../../lib/segments/segment-service-interface
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import { EventService } from '../../../lib/services';
let stores: IUnleashStores; let stores: IUnleashStores;
let db: ITestDb; let db: ITestDb;
@ -38,7 +39,8 @@ beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
db = await dbInit('playground_service_serial', config.getLogger); db = await dbInit('playground_service_serial', config.getLogger);
stores = db.stores; stores = db.stores;
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase, db.rawDatabase,
@ -55,6 +57,7 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
); );
@ -63,6 +66,7 @@ beforeAll(async () => {
config, config,
segmentService, segmentService,
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,

View File

@ -9,7 +9,7 @@ import { IUnleashStores } from '../../../lib/types';
import { IUser } from '../../../lib/server-impl'; import { IUser } from '../../../lib/server-impl';
import { SegmentService } from '../../../lib/services/segment-service'; import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker'; import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model'; import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
@ -19,6 +19,7 @@ let db: ITestDb;
let projectService; let projectService;
let groupService; let groupService;
let accessService; let accessService;
let eventService: EventService;
let projectHealthService; let projectHealthService;
let featureToggleService; let featureToggleService;
let favoritesService; let favoritesService;
@ -32,7 +33,8 @@ beforeAll(async () => {
name: 'Some Name', name: 'Some Name',
email: 'test@getunleash.io', email: 'test@getunleash.io',
}); });
groupService = new GroupService(stores, config); eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService); accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase, db.rawDatabase,
@ -52,14 +54,16 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
), ),
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService( projectService = new ProjectService(
stores, stores,
@ -68,6 +72,7 @@ beforeAll(async () => {
featureToggleService, featureToggleService,
groupService, groupService,
favoritesService, favoritesService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
projectHealthService = new ProjectHealthService( projectHealthService = new ProjectHealthService(

View File

@ -11,7 +11,7 @@ import EnvironmentService from '../../../lib/services/environment-service';
import IncompatibleProjectError from '../../../lib/error/incompatible-project-error'; import IncompatibleProjectError from '../../../lib/error/incompatible-project-error';
import { SegmentService } from '../../../lib/services/segment-service'; import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services'; import { EventService, FavoritesService } from '../../../lib/services';
import { FeatureEnvironmentEvent } from '../../../lib/types/events'; import { FeatureEnvironmentEvent } from '../../../lib/types/events';
import { addDays, subDays } from 'date-fns'; import { addDays, subDays } from 'date-fns';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model'; import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
@ -24,6 +24,7 @@ let db: ITestDb;
let projectService: ProjectService; let projectService: ProjectService;
let groupService: GroupService; let groupService: GroupService;
let accessService: AccessService; let accessService: AccessService;
let eventService: EventService;
let environmentService: EnvironmentService; let environmentService: EnvironmentService;
let featureToggleService: FeatureToggleService; let featureToggleService: FeatureToggleService;
let favoritesService: FavoritesService; let favoritesService: FavoritesService;
@ -53,7 +54,8 @@ beforeAll(async () => {
flags: { privateProjects: true }, flags: { privateProjects: true },
}, },
}); });
groupService = new GroupService(stores, config); eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService); accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel( const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase, db.rawDatabase,
@ -73,15 +75,17 @@ beforeAll(async () => {
stores, stores,
changeRequestAccessReadModel, changeRequestAccessReadModel,
config, config,
eventService,
privateProjectChecker, privateProjectChecker,
), ),
accessService, accessService,
eventService,
changeRequestAccessReadModel, changeRequestAccessReadModel,
privateProjectChecker, privateProjectChecker,
dependentFeaturesReadModel, dependentFeaturesReadModel,
); );
favoritesService = new FavoritesService(stores, config); favoritesService = new FavoritesService(stores, config, eventService);
environmentService = new EnvironmentService(stores, config); environmentService = new EnvironmentService(stores, config);
projectService = new ProjectService( projectService = new ProjectService(
stores, stores,
@ -90,6 +94,7 @@ beforeAll(async () => {
featureToggleService, featureToggleService,
groupService, groupService,
favoritesService, favoritesService,
eventService,
privateProjectChecker, privateProjectChecker,
); );
}); });
@ -1349,14 +1354,13 @@ test('should calculate average time to production', async () => {
await Promise.all( await Promise.all(
featureToggles.map((toggle) => { featureToggles.map((toggle) => {
return stores.eventStore.store( return eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled: true, enabled: true,
project: project.id, project: project.id,
featureName: toggle.name, featureName: toggle.name,
environment: 'default', environment: 'default',
createdBy: 'Fredrik', createdBy: 'Fredrik',
tags: [],
}), }),
); );
}), }),
@ -1407,19 +1411,19 @@ test('should calculate average time to production ignoring some items', async ()
await updateFeature(toggle.name, { await updateFeature(toggle.name, {
created_at: subDays(new Date(), 20), created_at: subDays(new Date(), 20),
}); });
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent(makeEvent(toggle.name)), new FeatureEnvironmentEvent(makeEvent(toggle.name)),
); );
// ignore events added after first enabled // ignore events added after first enabled
await updateEventCreatedAt(addDays(new Date(), 1), toggle.name); await updateEventCreatedAt(addDays(new Date(), 1), toggle.name);
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent(makeEvent(toggle.name)), new FeatureEnvironmentEvent(makeEvent(toggle.name)),
); );
// ignore toggles enabled in non-prod envs // ignore toggles enabled in non-prod envs
const devToggle = { name: 'dev-toggle' }; const devToggle = { name: 'dev-toggle' };
await featureToggleService.createFeatureToggle(project.id, devToggle, user); await featureToggleService.createFeatureToggle(project.id, devToggle, user);
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
...makeEvent(devToggle.name), ...makeEvent(devToggle.name),
environment: 'customEnv', environment: 'customEnv',
@ -1433,7 +1437,7 @@ test('should calculate average time to production ignoring some items', async ()
otherProjectToggle, otherProjectToggle,
user, user,
); );
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent(makeEvent(otherProjectToggle.name)), new FeatureEnvironmentEvent(makeEvent(otherProjectToggle.name)),
); );
@ -1444,7 +1448,7 @@ test('should calculate average time to production ignoring some items', async ()
nonReleaseToggle, nonReleaseToggle,
user, user,
); );
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent(makeEvent(nonReleaseToggle.name)), new FeatureEnvironmentEvent(makeEvent(nonReleaseToggle.name)),
); );
@ -1455,7 +1459,7 @@ test('should calculate average time to production ignoring some items', async ()
previouslyDeleteToggle, previouslyDeleteToggle,
user, user,
); );
await stores.eventStore.store( await eventService.storeEvent(
new FeatureEnvironmentEvent(makeEvent(previouslyDeleteToggle.name)), new FeatureEnvironmentEvent(makeEvent(previouslyDeleteToggle.name)),
); );
await updateEventCreatedAt( await updateEventCreatedAt(
@ -1625,14 +1629,13 @@ test('should return average time to production per toggle', async () => {
await Promise.all( await Promise.all(
featureToggles.map((toggle) => { featureToggles.map((toggle) => {
return stores.eventStore.store( return eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled: true, enabled: true,
project: project.id, project: project.id,
featureName: toggle.name, featureName: toggle.name,
environment: 'default', environment: 'default',
createdBy: 'Fredrik', createdBy: 'Fredrik',
tags: [],
}), }),
); );
}), }),
@ -1704,14 +1707,13 @@ test('should return average time to production per toggle for a specific project
await Promise.all( await Promise.all(
featureTogglesProject1.map((toggle) => { featureTogglesProject1.map((toggle) => {
return stores.eventStore.store( return eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled: true, enabled: true,
project: project1.id, project: project1.id,
featureName: toggle.name, featureName: toggle.name,
environment: 'default', environment: 'default',
createdBy: 'Fredrik', createdBy: 'Fredrik',
tags: [],
}), }),
); );
}), }),
@ -1719,14 +1721,13 @@ test('should return average time to production per toggle for a specific project
await Promise.all( await Promise.all(
featureTogglesProject2.map((toggle) => { featureTogglesProject2.map((toggle) => {
return stores.eventStore.store( return eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled: true, enabled: true,
project: project2.id, project: project2.id,
featureName: toggle.name, featureName: toggle.name,
environment: 'default', environment: 'default',
createdBy: 'Fredrik', createdBy: 'Fredrik',
tags: [],
}), }),
); );
}), }),
@ -1783,14 +1784,13 @@ test('should return average time to production per toggle and include archived t
await Promise.all( await Promise.all(
featureTogglesProject1.map((toggle) => { featureTogglesProject1.map((toggle) => {
return stores.eventStore.store( return eventService.storeEvent(
new FeatureEnvironmentEvent({ new FeatureEnvironmentEvent({
enabled: true, enabled: true,
project: project1.id, project: project1.id,
featureName: toggle.name, featureName: toggle.name,
environment: 'default', environment: 'default',
createdBy: 'Fredrik', createdBy: 'Fredrik',
tags: [],
}), }),
); );
}), }),

View File

@ -12,7 +12,7 @@ import { IUser } from '../../../lib/types/user';
import SettingService from '../../../lib/services/setting-service'; import SettingService from '../../../lib/services/setting-service';
import FakeSettingStore from '../../fixtures/fake-setting-store'; import FakeSettingStore from '../../fixtures/fake-setting-store';
import { GroupService } from '../../../lib/services/group-service'; import { GroupService } from '../../../lib/services/group-service';
import FakeEventStore from '../../fixtures/fake-event-store'; import { EventService } from '../../../lib/services';
const config: IUnleashConfig = createTestConfig(); const config: IUnleashConfig = createTestConfig();
@ -28,7 +28,8 @@ let sessionService: SessionService;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('reset_token_service_serial', getLogger); db = await dbInit('reset_token_service_serial', getLogger);
stores = db.stores; stores = db.stores;
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService); accessService = new AccessService(stores, config, groupService);
resetTokenService = new ResetTokenService(stores, config); resetTokenService = new ResetTokenService(stores, config);
sessionService = new SessionService(stores, config); sessionService = new SessionService(stores, config);
@ -36,15 +37,16 @@ beforeAll(async () => {
const settingService = new SettingService( const settingService = new SettingService(
{ {
settingStore: new FakeSettingStore(), settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
}, },
config, config,
eventService,
); );
userService = new UserService(stores, config, { userService = new UserService(stores, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });

View File

@ -7,6 +7,7 @@ import {
SETTING_DELETED, SETTING_DELETED,
SETTING_UPDATED, SETTING_UPDATED,
} from '../../../lib/types/events'; } from '../../../lib/types/events';
import { EventService } from '../../../lib/services';
let stores: IUnleashStores; let stores: IUnleashStores;
let db; let db;
@ -16,7 +17,8 @@ beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
db = await dbInit('setting_service_serial', config.getLogger); db = await dbInit('setting_service_serial', config.getLogger);
stores = db.stores; stores = db.stores;
service = new SettingService(stores, config); const eventService = new EventService(stores, config);
service = new SettingService(stores, config, eventService);
}); });
beforeEach(async () => { beforeEach(async () => {
await stores.eventStore.deleteAll(); await stores.eventStore.deleteAll();

View File

@ -3,6 +3,7 @@ import dbInit from '../helpers/database-init';
import StateService from '../../../lib/services/state-service'; import StateService from '../../../lib/services/state-service';
import oldFormat from '../../examples/variantsexport_v3.json'; import oldFormat from '../../examples/variantsexport_v3.json';
import { WeightType } from '../../../lib/types/model'; import { WeightType } from '../../../lib/types/model';
import { EventService } from '../../../lib/services';
let stores; let stores;
let db; let db;
@ -12,7 +13,8 @@ beforeAll(async () => {
const config = createTestConfig(); const config = createTestConfig();
db = await dbInit('state_service_serial', config.getLogger); db = await dbInit('state_service_serial', config.getLogger);
stores = db.stores; stores = db.stores;
stateService = new StateService(stores, config); const eventService = new EventService(stores, config);
stateService = new StateService(stores, config, eventService);
}); });
afterAll(async () => { afterAll(async () => {

View File

@ -17,6 +17,7 @@ import { GroupService } from '../../../lib/services/group-service';
import { randomId } from '../../../lib/util/random-id'; import { randomId } from '../../../lib/util/random-id';
import { BadDataError } from '../../../lib/error'; import { BadDataError } from '../../../lib/error';
import PasswordMismatch from '../../../lib/error/password-mismatch'; import PasswordMismatch from '../../../lib/error/password-mismatch';
import { EventService } from '../../../lib/services';
let db; let db;
let stores; let stores;
@ -31,17 +32,19 @@ beforeAll(async () => {
db = await dbInit('user_service_serial', getLogger); db = await dbInit('user_service_serial', getLogger);
stores = db.stores; stores = db.stores;
const config = createTestConfig(); const config = createTestConfig();
const groupService = new GroupService(stores, config); const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const resetTokenService = new ResetTokenService(stores, config); const resetTokenService = new ResetTokenService(stores, config);
const emailService = new EmailService(undefined, config.getLogger); const emailService = new EmailService(undefined, config.getLogger);
sessionService = new SessionService(stores, config); sessionService = new SessionService(stores, config);
settingService = new SettingService(stores, config); settingService = new SettingService(stores, config, eventService);
userService = new UserService(stores, config, { userService = new UserService(stores, config, {
accessService, accessService,
resetTokenService, resetTokenService,
emailService, emailService,
eventService,
sessionService, sessionService,
settingService, settingService,
}); });

View File

@ -198,7 +198,6 @@ test('Should get all events of type', async () => {
featureName: data.name, featureName: data.name,
createdBy: 'test-user', createdBy: 'test-user',
data, data,
tags: [],
}) })
: new FeatureDeletedEvent({ : new FeatureDeletedEvent({
project: data.project, project: data.project,