1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-31 01:16:01 +02: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 EnvironmentStore from '../../db/environment-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 FakeEventStore from '../../../test/fixtures/fake-event-store';
import { FakeAccountStore } from '../../../test/fixtures/fake-account-store';
import FakeRoleStore from '../../../test/fixtures/fake-role-store';
import FakeEnvironmentStore from '../../../test/fixtures/fake-environment-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 = (
db: Db,
@ -24,9 +26,15 @@ export const createAccessService = (
const roleStore = new RoleStore(db, eventBus, getLogger);
const environmentStore = new EnvironmentStore(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(
{ groupStore, eventStore, accountStore },
{ groupStore, accountStore },
{ getLogger },
eventService,
);
return new AccessService(
@ -46,9 +54,15 @@ export const createFakeAccessService = (
const roleStore = new FakeRoleStore();
const environmentStore = new FakeEnvironmentStore();
const accessStore = new FakeAccessStore(roleStore);
const featureTagStore = new FakeFeatureTagStore();
const eventService = new EventService(
{ eventStore, featureTagStore },
config,
);
const groupService = new GroupService(
{ groupStore, eventStore, accountStore },
{ groupStore, accountStore },
{ getLogger },
eventService,
);
return new AccessService(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,11 @@ import { createTestConfig } from '../../test/config/test-config';
import FakeEventStore from '../../test/fixtures/fake-event-store';
import { randomId } from '../util/random-id';
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 { frontendSettingsKey } from '../../lib/types/settings/frontend-settings';
import { minutesToMilliseconds } from 'date-fns';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
const createSettingService = (
frontendApiOrigins: string[],
@ -17,11 +18,14 @@ const createSettingService = (
const stores = {
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
featureTagStore: new FakeFeatureTagStore(),
projectStore: new FakeProjectStore(),
};
const eventService = new EventService(stores, config);
const services = {
settingService: new SettingService(stores, config),
settingService: new SettingService(stores, config, eventService),
};
return {

View File

@ -22,7 +22,11 @@ async function getSetup(anonymise: boolean = false) {
const services = createServices(stores, config);
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 () => {
@ -38,14 +42,13 @@ test('should get empty events list via admin', async () => {
});
test('should get events list via admin', async () => {
const { request, base, eventStore } = await getSetup();
eventStore.store(
const { request, base, eventService } = await getSetup();
eventService.storeEvent(
new FeatureCreatedEvent({
createdBy: 'some@email.com',
data: { name: 'test', project: 'default' },
featureName: 'test',
project: 'default',
tags: [],
}),
);
const { body } = await request
@ -58,14 +61,13 @@ test('should get events list via admin', async () => {
});
test('should anonymise events list via admin', async () => {
const { request, base, eventStore } = await getSetup(true);
eventStore.store(
const { request, base, eventService } = await getSetup(true);
eventService.storeEvent(
new FeatureCreatedEvent({
createdBy: 'some@email.com',
data: { name: 'test', project: 'default' },
featureName: 'test',
project: 'default',
tags: [],
}),
);
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 email2 = 'test2@email.com';
const { request, base, eventStore } = await getSetup(true);
eventStore.store(
const { request, base, eventService } = await getSetup(true);
eventService.storeEvent(
new ProjectUserAddedEvent({
createdBy: 'some@email.com',
data: { name: 'test', project: 'default', email: email1 },
project: 'default',
}),
);
eventStore.store(
eventService.storeEvent(
new ProjectUserRemovedEvent({
createdBy: 'some@email.com',
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 () => {
const testUsername = 'test-username';
const { request, base, eventStore } = await getSetup(true);
eventStore.store(
const { request, base, eventService } = await getSetup(true);
eventService.storeEvent(
new ProjectAccessAddedEvent({
createdBy: 'some@email.com',
data: {

View File

@ -13,6 +13,8 @@ import { GroupService } from '../services/group-service';
import FakeEventStore from '../../test/fixtures/fake-event-store';
import { IRole } from 'lib/types/stores/access-store';
import { IGroup } from 'lib/types';
import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
function getSetup(customRootRolesKillSwitch: boolean = true) {
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> => {
return { id: 1, type: 'custom', name: 'project role' };
};
const eventService = new EventService(
{ eventStore, featureTagStore: new FakeFeatureTagStore() },
config,
);
const groupService = new GroupService(
{ groupStore, eventStore, accountStore },
{ groupStore, accountStore },
{ getLogger },
eventService,
);
const accessService = new AccessService(

View File

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

View File

@ -14,6 +14,7 @@ import AddonService from './addon-service';
import { IAddonDto } from '../types/stores/addon-store';
import SimpleAddon from './addon-service-test-simple-addon';
import { IAddonProviders } from '../addons';
import EventService from './event-service';
const MASKED_VALUE = '*****';
@ -21,7 +22,12 @@ let addonProvider: IAddonProviders;
function getSetup() {
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() };
return {
@ -33,8 +39,10 @@ function getSetup() {
server: { unleashUrl: 'http://test' },
},
tagTypeService,
eventService,
addonProvider,
),
eventService,
stores,
tagTypeService,
};
@ -77,7 +85,7 @@ test('should not allow addon-config for unknown provider', async () => {
});
test('should trigger simple-addon eventHandler', async () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const config = {
provider: 'simple',
@ -93,7 +101,7 @@ test('should trigger simple-addon eventHandler', async () => {
await addonService.createAddon(config, 'me@mail.com');
// Feature toggle was created
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const config = {
provider: 'simple',
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 stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const desiredProject = 'desired';
const otherProject = 'other';
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 stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProject,
@ -169,7 +177,7 @@ test('should trigger event handler if project for event is one of the desired pr
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const desiredProjects = ['desired', 'desired2'];
const otherProject = 'other';
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 stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[0],
@ -214,7 +222,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: otherProject,
@ -224,7 +232,7 @@ test('should trigger events for multiple projects if addon is setup to filter mu
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const desiredEnvironment = 'desired';
const otherEnvironment = 'other';
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 stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredEnvironment,
@ -273,7 +281,7 @@ test('should filter events on environment if addon is setup to filter for it', a
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const filteredEnvironment = 'filtered';
const config = {
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 stores.eventStore.store(globalEventWithNoEnvironment);
await eventService.storeEvent(globalEventWithNoEnvironment);
const simpleProvider = addonService.addonProviders.simple;
// @ts-expect-error
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const filteredProject = 'filtered';
const config = {
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 stores.eventStore.store(globalEventWithNoProject);
await eventService.storeEvent(globalEventWithNoProject);
const simpleProvider = addonService.addonProviders.simple;
// @ts-expect-error
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const desiredProjects = ['desired', 'desired2'];
const otherProject = 'other';
const config = {
@ -381,7 +389,7 @@ test('should support wildcard option for filtering addons', async () => {
};
await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[0],
@ -391,7 +399,7 @@ test('should support wildcard option for filtering addons', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: otherProject,
@ -401,7 +409,7 @@ test('should support wildcard option for filtering addons', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const desiredProjects = ['desired1', 'desired2', 'desired3'];
const desiredEnvironments = ['env1', 'env2', 'env3'];
const config = {
@ -445,7 +453,7 @@ test('Should support filtering by both project and environment', async () => {
'desired-toggle3',
];
await addonService.createAddon(config, 'me@mail.com');
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[0],
@ -456,7 +464,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[0],
@ -467,7 +475,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[2],
@ -478,7 +486,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: desiredProjects[2],
@ -489,7 +497,7 @@ test('Should support filtering by both project and environment', async () => {
strategies: [{ name: 'default' }],
},
});
await stores.eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
createdBy: 'some@user.com',
project: 'wrongproject',
@ -556,7 +564,7 @@ test('should create tag type for simple-addon', async () => {
});
test('should store ADDON_CONFIG_CREATE event', async () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const config = {
provider: 'simple',
@ -571,7 +579,7 @@ test('should store ADDON_CONFIG_CREATE event', async () => {
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[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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const config: IAddonDto = {
description: '',
@ -597,7 +605,7 @@ test('should store ADDON_CONFIG_UPDATE event', async () => {
const updated = { ...addonConfig, description: 'test' };
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[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 () => {
const { addonService, stores } = getSetup();
const { addonService, eventService } = getSetup();
const config: IAddonDto = {
provider: 'simple',
@ -622,7 +630,7 @@ test('should store ADDON_CONFIG_REMOVE event', async () => {
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[2].type).toBe(ADDON_CONFIG_DELETED);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -64,6 +64,7 @@ import { BadDataError, PermissionError } from '../error';
import { ProjectDoraMetricsSchema } from 'lib/openapi';
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import EventService from './event-service';
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
@ -113,6 +114,8 @@ export default class ProjectService {
private favoritesService: FavoritesService;
private eventService: EventService;
private projectStatsStore: IProjectStatsStore;
private flagResolver: IFlagResolver;
@ -145,6 +148,7 @@ export default class ProjectService {
featureToggleService: FeatureToggleService,
groupService: GroupService,
favoriteService: FavoritesService,
eventService: EventService,
privateProjectChecker: IPrivateProjectChecker,
) {
this.projectStore = projectStore;
@ -159,6 +163,7 @@ export default class ProjectService {
this.privateProjectChecker = privateProjectChecker;
this.accountStore = accountStore;
this.groupService = groupService;
this.eventService = eventService;
this.projectStatsStore = projectStatsStore;
this.logger = config.getLogger('services/project-service.js');
this.flagResolver = config.flagResolver;
@ -246,7 +251,7 @@ export default class ProjectService {
await this.accessService.createDefaultProjectRoles(user, data.id);
await this.eventStore.store({
await this.eventService.storeEvent({
type: PROJECT_CREATED,
createdBy: getCreatedBy(user),
data,
@ -284,7 +289,7 @@ export default class ProjectService {
await this.projectStore.updateProjectEnterpriseSettings(updatedProject);
await this.eventStore.store({
await this.eventService.storeEvent({
type: PROJECT_UPDATED,
project: updatedProject.id,
createdBy: getCreatedBy(user),
@ -381,7 +386,7 @@ export default class ProjectService {
await this.projectStore.delete(id);
await this.eventStore.store({
await this.eventService.storeEvent({
type: PROJECT_DELETED,
createdBy: getCreatedBy(user),
project: id,
@ -434,7 +439,7 @@ export default class ProjectService {
await this.accessService.addUserToRole(userId, role.id, projectId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectUserAddedEvent({
project: projectId,
createdBy: createdBy || 'system-user',
@ -462,7 +467,7 @@ export default class ProjectService {
const user = await this.accountStore.get(userId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectUserRemovedEvent({
project: projectId,
createdBy,
@ -488,7 +493,7 @@ export default class ProjectService {
await this.accessService.removeUserAccess(projectId, userId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessUserRolesDeleted({
project: projectId,
createdBy,
@ -512,7 +517,7 @@ export default class ProjectService {
await this.accessService.removeGroupAccess(projectId, groupId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessUserRolesDeleted({
project: projectId,
createdBy,
@ -547,7 +552,7 @@ export default class ProjectService {
project.id,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectGroupAddedEvent({
project: project.id,
createdBy: modifiedBy,
@ -582,7 +587,7 @@ export default class ProjectService {
project.id,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectGroupRemovedEvent({
project: projectId,
createdBy: modifiedBy,
@ -609,7 +614,7 @@ export default class ProjectService {
createdBy,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessAddedEvent({
project: projectId,
createdBy,
@ -637,7 +642,7 @@ export default class ProjectService {
createdBy,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessAddedEvent({
project: projectId,
createdBy,
@ -665,7 +670,7 @@ export default class ProjectService {
userId,
roles,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessUserRolesUpdated({
project: projectId,
createdBy: createdByUserName,
@ -697,7 +702,7 @@ export default class ProjectService {
roles,
createdBy,
);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectAccessGroupRolesUpdated({
project: projectId,
createdBy,
@ -823,7 +828,7 @@ export default class ProjectService {
);
const role = await this.findProjectRole(projectId, roleId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectUserUpdateRoleEvent({
project: projectId,
createdBy,
@ -877,7 +882,7 @@ export default class ProjectService {
);
const role = await this.findProjectGroupRole(projectId, roleId);
await this.eventStore.store(
await this.eventService.storeEvent(
new ProjectGroupUpdateRoleEvent({
project: projectId,
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 { CreateInvitedUserSchema } from 'lib/openapi/spec/create-invited-user-schema';
import { RoleName } from '../types/model';
import { IEventStore } from '../types/stores/event-store';
import {
PublicSignupTokenCreatedEvent,
PublicSignupTokenUpdatedEvent,
@ -18,16 +17,17 @@ import UserService from './user-service';
import { IUser } from '../types/user';
import { URL } from 'url';
import { add } from 'date-fns';
import EventService from './event-service';
export class PublicSignupTokenService {
private store: IPublicSignupTokenStore;
private roleStore: IRoleStore;
private eventStore: IEventStore;
private userService: UserService;
private eventService: EventService;
private logger: Logger;
private timer: NodeJS.Timeout;
@ -38,18 +38,15 @@ export class PublicSignupTokenService {
{
publicSignupTokenStore,
roleStore,
eventStore,
}: Pick<
IUnleashStores,
'publicSignupTokenStore' | 'roleStore' | 'eventStore'
>,
}: Pick<IUnleashStores, 'publicSignupTokenStore' | 'roleStore'>,
config: Pick<IUnleashConfig, 'getLogger' | 'authentication' | 'server'>,
userService: UserService,
eventService: EventService,
) {
this.store = publicSignupTokenStore;
this.userService = userService;
this.eventService = eventService;
this.roleStore = roleStore;
this.eventStore = eventStore;
this.logger = config.getLogger(
'/services/public-signup-token-service.ts',
);
@ -84,7 +81,7 @@ export class PublicSignupTokenService {
createdBy: string,
): Promise<PublicSignupTokenSchema> {
const result = await this.store.update(secret, { expiresAt, enabled });
await this.eventStore.store(
await this.eventService.storeEvent(
new PublicSignupTokenUpdatedEvent({
createdBy,
data: { secret, enabled, expiresAt },
@ -103,7 +100,7 @@ export class PublicSignupTokenService {
rootRole: token.role.id,
});
await this.store.addTokenUser(secret, user.id);
await this.eventStore.store(
await this.eventService.storeEvent(
new PublicSignupTokenUserAddedEvent({
createdBy: 'System',
data: { secret, userId: user.id },
@ -133,7 +130,7 @@ export class PublicSignupTokenService {
};
const token = await this.store.insert(newToken);
await this.eventStore.store(
await this.eventService.storeEvent(
new PublicSignupTokenCreatedEvent({
createdBy: createdBy,
data: token,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,12 +5,12 @@ import {
import dbInit, { ITestDb } from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger';
import { FEATURE_CREATED, IBaseEvent } from '../../../../lib/types/events';
import { IEventStore } from '../../../../lib/types/stores/event-store';
import { randomId } from '../../../../lib/util/random-id';
import { EventService } from '../../../../lib/services';
let app: IUnleashTest;
let db: ITestDb;
let eventStore: IEventStore;
let eventService: EventService;
beforeAll(async () => {
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 () => {
await eventStore.deleteAll();
await db.stores.eventStore.deleteAll();
});
afterAll(async () => {
@ -50,7 +50,7 @@ test('returns events given a name', async () => {
});
test('Can filter by project', async () => {
await eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
project: 'something-else',
data: { id: 'some-other-feature' },
@ -58,7 +58,7 @@ test('Can filter by project', async () => {
createdBy: 'test-user',
environment: 'test',
});
await eventStore.store({
await eventService.storeEvent({
type: FEATURE_CREATED,
project: 'default',
data: { id: 'feature' },
@ -96,7 +96,7 @@ test('can search for events', async () => {
await Promise.all(
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 FakeSettingStore from '../../../fixtures/fake-setting-store';
import { GroupService } from '../../../../lib/services/group-service';
import FakeEventStore from '../../../fixtures/fake-event-store';
import { EventService } from '../../../../lib/services';
let app;
let stores;
@ -49,7 +49,8 @@ beforeAll(async () => {
db = await dbInit('reset_password_api_serial', getLogger);
stores = db.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);
const emailService = new EmailService(config.email, config.getLogger);
const sessionStore = new SessionStore(
@ -61,14 +62,15 @@ beforeAll(async () => {
const settingService = new SettingService(
{
settingStore: new FakeSettingStore(),
eventStore: new FakeEventStore(),
},
config,
eventService,
);
userService = new UserService(stores, config, {
accessService,
resetTokenService,
emailService,
eventService,
sessionService,
settingService,
});

View File

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

View File

@ -20,7 +20,7 @@ import { DEFAULT_PROJECT } from '../../../lib/types/project';
import { ALL_PROJECTS } from '../../../lib/util/constants';
import { SegmentService } from '../../../lib/services/segment-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 { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
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 stores: IUnleashStores;
let accessService: AccessService;
let eventService: EventService;
let groupService: GroupService;
let featureToggleService;
let favoritesService;
@ -237,7 +238,8 @@ beforeAll(async () => {
// @ts-ignore
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);
const roles = await accessService.getRootRoles();
editorRole = roles.find((r) => r.name === RoleName.EDITOR);
@ -261,14 +263,16 @@ beforeAll(async () => {
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
favoritesService = new FavoritesService(stores, config);
favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService(
stores,
config,
@ -276,6 +280,7 @@ beforeAll(async () => {
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);

View File

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

View File

@ -10,7 +10,7 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { AccessService } from '../../../lib/services/access-service';
import { SegmentService } from '../../../lib/services/segment-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 { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
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);
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 changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
@ -47,9 +48,11 @@ beforeAll(async () => {
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
@ -65,7 +68,7 @@ beforeAll(async () => {
name: 'Some Name',
email: 'test@getunleash.io',
});
favoritesService = new FavoritesService(stores, config);
favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService(
stores,
config,
@ -73,12 +76,13 @@ beforeAll(async () => {
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);
await projectService.createProject(project, user);
apiTokenService = new ApiTokenService(stores, config);
apiTokenService = new ApiTokenService(stores, config, eventService);
});
afterAll(async () => {

View File

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

View File

@ -3,10 +3,12 @@ import getLogger from '../../fixtures/no-logger';
import { createTestConfig } from '../../config/test-config';
import { GroupService } from '../../../lib/services/group-service';
import GroupStore from '../../../lib/db/group-store';
import { EventService } from '../../../lib/services';
let stores;
let db: ITestDb;
let eventService: EventService;
let groupService: GroupService;
let groupStore: GroupStore;
let user;
@ -21,7 +23,8 @@ beforeAll(async () => {
const config = createTestConfig({
getLogger,
});
groupService = new GroupService(stores, config);
eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
groupStore = stores.groupStore;
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 { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import { EventService } from '../../../lib/services';
let stores: IUnleashStores;
let db: ITestDb;
@ -38,7 +39,8 @@ beforeAll(async () => {
const config = createTestConfig();
db = await dbInit('playground_service_serial', config.getLogger);
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 changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
@ -55,6 +57,7 @@ beforeAll(async () => {
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
);
@ -63,6 +66,7 @@ beforeAll(async () => {
config,
segmentService,
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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