1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-26 01:17:00 +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 {
@ -135,8 +139,8 @@ test('corsOriginMiddleware with caching enabled', async () => {
/*
This is needed because it is not enough to fake time to test the
updated cache, we also need to make sure that all promises are
executed and completed, in the right order.
updated cache, we also need to make sure that all promises are
executed and completed, in the right order.
*/
await new Promise<void>((resolve) =>
process.nextTick(async () => {

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(
potentiallyStaleFeatures
.filter((feature) => feature.potentiallyStale)
.map(
async ({ name, project }) =>
new PotentiallyStaleOnEvent({
featureName: name,
project,
tags: await this.tagStore.getAllTagsForFeature(
name,
),
}),
),
),
return this.eventService.storeEvents(
potentiallyStaleFeatures
.filter((feature) => feature.potentiallyStale)
.map(
({ name, project }) =>
new PotentiallyStaleOnEvent({
featureName: name,
project,
}),
),
);
}
}

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, {
getLogger,
}),
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, {
getLogger,
}),
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, {
getLogger,
});
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,