From 847e3129151bef631435825d3afb1f9d21c723ff Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Fri, 29 Nov 2024 13:28:39 +0100 Subject: [PATCH 1/9] Reapply "task: enabled in OSS." (#8892) This reverts commit 3c01813826e8d9c9ccb750eaa01017b7b59f28dd. --- .../__snapshots__/create-config.test.ts.snap | 1 + src/lib/create-config.ts | 5 ++ src/lib/db/feature-environment-store.ts | 31 ++++++- src/lib/db/index.ts | 14 +--- .../features/access/createAccessService.ts | 2 +- .../api-tokens/createApiTokenService.ts | 2 +- .../client-feature-toggle-store.ts | 18 +++- .../createClientFeatureToggleService.ts | 13 +-- .../createExportImportService.ts | 9 +- .../createFeatureLifecycle.ts | 4 +- .../createFeatureToggleService.ts | 14 +--- .../feature-toggle/feature-toggle-store.ts | 10 +++ .../createInstanceStatsService.ts | 9 +- .../createEnvironmentService.ts | 11 +-- .../project-environments/environment-store.ts | 37 +++++++-- .../createProjectInsightsService.ts | 7 +- .../createProjectStatusService.ts | 7 +- .../features/project/createProjectService.ts | 11 +-- src/lib/features/project/project-store.ts | 16 +++- src/lib/types/option.ts | 2 + .../e2e/api/admin/environment-oss.e2e.test.ts | 68 +++++++++++++++ .../e2e/api/client/feature-oss.e2e.test.ts | 82 +++++++++++++++++++ 22 files changed, 284 insertions(+), 89 deletions(-) create mode 100644 src/test/e2e/api/admin/environment-oss.e2e.test.ts create mode 100644 src/test/e2e/api/client/feature-oss.e2e.test.ts diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index d056cc87f375..6248cd1cfb8c 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -85,6 +85,7 @@ exports[`should create default config 1`] = ` }, "inlineSegmentConstraints": true, "isEnterprise": false, + "isOss": false, "listen": { "host": undefined, "port": 4242, diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index bfd59d732458..015e7a9973ac 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -620,6 +620,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { Boolean(options.enterpriseVersion) && ui.environment?.toLowerCase() !== 'pro'; + const isTest = process.env.NODE_ENV === 'test'; + const isOss = isTest + ? options.isOss || false + : !isEnterprise && ui.environment !== 'pro'; const metricsRateLimiting = loadMetricsRateLimitingConfig(options); const rateLimiting = loadRateLimitingConfig(options); @@ -760,6 +764,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { publicFolder: options.publicFolder, disableScheduler: options.disableScheduler, isEnterprise: isEnterprise, + isOss: isOss, metricsRateLimiting, rateLimiting, feedbackUriPath, diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts index 6b3cb7392539..3bb80159bff9 100644 --- a/src/lib/db/feature-environment-store.ts +++ b/src/lib/db/feature-environment-store.ts @@ -3,13 +3,14 @@ import type { FeatureEnvironmentKey, IFeatureEnvironmentStore, } from '../types/stores/feature-environment-store'; -import type { Logger, LogProvider } from '../logger'; +import type { Logger } from '../logger'; import metricsHelper from '../util/metrics-helper'; import { DB_TIME } from '../metric-events'; import type { IFeatureEnvironment, IVariant } from '../types/model'; import NotFoundError from '../error/notfound-error'; import { v4 as uuidv4 } from 'uuid'; import type { Db } from './db'; +import type { IUnleashConfig } from '../types'; const T = { featureEnvs: 'feature_environments', @@ -36,7 +37,12 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { private readonly timer: Function; - constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) { + private readonly isOss: boolean; + constructor( + db: Db, + eventBus: EventEmitter, + { getLogger, isOss }: Pick<IUnleashConfig, 'getLogger' | 'isOss'>, + ) { this.db = db; this.logger = getLogger('feature-environment-store.ts'); this.timer = (action) => @@ -44,6 +50,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { store: 'feature-environments', action, }); + this.isOss = isOss; } async delete({ @@ -96,11 +103,30 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { ); } + addOssFilterIfNeeded(queryBuilder) { + if (this.isOss) { + return queryBuilder + .join( + 'environments', + 'environments.name', + '=', + `${T.featureEnvs}.environment`, + ) + .whereIn('environments.name', [ + 'default', + 'development', + 'production', + ]); + } + return queryBuilder; + } + async getAll(query?: Object): Promise<IFeatureEnvironment[]> { let rows = this.db(T.featureEnvs); if (query) { rows = rows.where(query); } + this.addOssFilterIfNeeded(rows); return (await rows).map((r) => ({ enabled: r.enabled, featureName: r.feature_name, @@ -119,6 +145,7 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { if (environment) { rows = rows.where({ environment }); } + this.addOssFilterIfNeeded(rows); return (await rows).map((r) => ({ enabled: r.enabled, featureName: r.feature_name, diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 72ddcd4b497b..3722d91d46fd 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -94,12 +94,7 @@ export const createStores = ( settingStore: new SettingStore(db, getLogger), userStore: new UserStore(db, getLogger, config.flagResolver), accountStore: new AccountStore(db, getLogger), - projectStore: new ProjectStore( - db, - eventBus, - getLogger, - config.flagResolver, - ), + projectStore: new ProjectStore(db, eventBus, config), tagStore: new TagStore(db, eventBus, getLogger), tagTypeStore: new TagTypeStore(db, eventBus, getLogger), addonStore: new AddonStore(db, eventBus, getLogger), @@ -122,15 +117,14 @@ export const createStores = ( clientFeatureToggleStore: new FeatureToggleClientStore( db, eventBus, - getLogger, - config.flagResolver, + config, ), - environmentStore: new EnvironmentStore(db, eventBus, getLogger), + environmentStore: new EnvironmentStore(db, eventBus, config), featureTagStore: new FeatureTagStore(db, eventBus, getLogger), featureEnvironmentStore: new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ), userSplashStore: new UserSplashStore(db, eventBus, getLogger), roleStore: new RoleStore(db, eventBus, getLogger), diff --git a/src/lib/features/access/createAccessService.ts b/src/lib/features/access/createAccessService.ts index 26791fc82fac..c381c0ed278c 100644 --- a/src/lib/features/access/createAccessService.ts +++ b/src/lib/features/access/createAccessService.ts @@ -25,7 +25,7 @@ export const createAccessService = ( const groupStore = new GroupStore(db); const accountStore = new AccountStore(db, getLogger); const roleStore = new RoleStore(db, eventBus, getLogger); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const accessStore = new AccessStore(db, eventBus, getLogger); const eventService = createEventsService(db, config); const groupService = new GroupService( diff --git a/src/lib/features/api-tokens/createApiTokenService.ts b/src/lib/features/api-tokens/createApiTokenService.ts index 18b236c628c8..0dec7de7a457 100644 --- a/src/lib/features/api-tokens/createApiTokenService.ts +++ b/src/lib/features/api-tokens/createApiTokenService.ts @@ -21,7 +21,7 @@ export const createApiTokenService = ( getLogger, config.flagResolver, ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); return new ApiTokenService( diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts index b93d94daeeda..400ff737d507 100644 --- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts +++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts @@ -1,7 +1,7 @@ import { Knex } from 'knex'; import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import type { IFeatureToggleClient, IFeatureToggleClientStore, @@ -9,6 +9,7 @@ import type { IFlagResolver, IStrategyConfig, ITag, + IUnleashConfig, PartialDeep, } from '../../types'; import { @@ -46,11 +47,16 @@ export default class FeatureToggleClientStore private flagResolver: IFlagResolver; + private readonly isOss: boolean; + constructor( db: Db, eventBus: EventEmitter, - getLogger: LogProvider, - flagResolver: IFlagResolver, + { + getLogger, + flagResolver, + isOss, + }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>, ) { this.db = db; this.logger = getLogger('feature-toggle-client-store.ts'); @@ -60,6 +66,7 @@ export default class FeatureToggleClientStore action, }); this.flagResolver = flagResolver; + this.isOss = isOss; } private async getAll({ @@ -72,7 +79,6 @@ export default class FeatureToggleClientStore const isPlayground = requestType === 'playground'; const environment = featureQuery?.environment || DEFAULT_ENV; const stopTimer = this.timer(`getAllBy${requestType}`); - let selectColumns = [ 'features.name as name', 'features.description as description', @@ -103,6 +109,10 @@ export default class FeatureToggleClientStore let query = this.db('features') .modify(FeatureToggleStore.filterByArchived, archived) + .modify( + FeatureToggleStore.filterByProjectsAccessibleByOss, + this.isOss, + ) .leftJoin( this.db('feature_strategies') .select('*') diff --git a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts index f48d98e0d136..17be891a7a7d 100644 --- a/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts +++ b/src/lib/features/client-feature-toggles/createClientFeatureToggleService.ts @@ -11,13 +11,10 @@ export const createClientFeatureToggleService = ( db: Db, config: IUnleashConfig, ): ClientFeatureToggleService => { - const { getLogger, eventBus, flagResolver } = config; - const featureToggleClientStore = new FeatureToggleClientStore( db, - eventBus, - getLogger, - flagResolver, + config.eventBus, + config, ); const segmentReadModel = new SegmentReadModel(db); @@ -30,7 +27,7 @@ export const createClientFeatureToggleService = ( }, segmentReadModel, clientFeatureToggleCache, - { getLogger, flagResolver }, + config, ); return clientFeatureToggleService; @@ -39,8 +36,6 @@ export const createClientFeatureToggleService = ( export const createFakeClientFeatureToggleService = ( config: IUnleashConfig, ): ClientFeatureToggleService => { - const { getLogger, flagResolver } = config; - const fakeClientFeatureToggleStore = new FakeClientFeatureToggleStore(); const fakeSegmentReadModel = new FakeSegmentReadModel(); @@ -51,7 +46,7 @@ export const createFakeClientFeatureToggleService = ( }, fakeSegmentReadModel, null, - { getLogger, flagResolver }, + config, ); return clientFeatureToggleService; diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts index b36b6e0fabf3..69bebf187066 100644 --- a/src/lib/features/export-import-toggles/createExportImportService.ts +++ b/src/lib/features/export-import-toggles/createExportImportService.ts @@ -150,12 +150,7 @@ export const deferredExportImportTogglesService = ( ); const tagStore = new TagStore(db, eventBus, getLogger); const tagTypeStore = new TagTypeStore(db, eventBus, getLogger); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const featureTagStore = new FeatureTagStore(db, eventBus, getLogger); const strategyStore = new StrategyStore(db, getLogger); const contextFieldStore = new ContextFieldStore( @@ -172,7 +167,7 @@ export const deferredExportImportTogglesService = ( const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const eventStore = new EventStore(db, getLogger); const accessService = createAccessService(db, config); diff --git a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts index a97b540b6043..ed907e9f894b 100644 --- a/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts +++ b/src/lib/features/feature-lifecycle/createFeatureLifecycle.ts @@ -19,11 +19,11 @@ export const createFeatureLifecycleService = const { eventBus, getLogger } = config; const eventStore = new EventStore(db, getLogger); const featureLifecycleStore = new FeatureLifecycleStore(db); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const eventService = createEventsService(db, config); const featureLifecycleService = new FeatureLifecycleService( diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts index d5d9d21bae87..b12eb3adfc17 100644 --- a/src/lib/features/feature-toggle/createFeatureToggleService.ts +++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts @@ -80,19 +80,13 @@ export const createFeatureToggleService = ( const featureToggleClientStore = new FeatureToggleClientStore( db, eventBus, - getLogger, - flagResolver, - ); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, + config, ); + const projectStore = new ProjectStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const contextFieldStore = new ContextFieldStore( db, @@ -105,7 +99,7 @@ export const createFeatureToggleService = ( 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 environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); const groupService = new GroupService( { groupStore, accountStore }, diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts index fe412fb9986a..861d1af92c5e 100644 --- a/src/lib/features/feature-toggle/feature-toggle-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-store.ts @@ -439,6 +439,16 @@ export default class FeatureToggleStore implements IFeatureToggleStore { : queryBuilder.whereNull('archived_at'); }; + static filterByProjectsAccessibleByOss: Knex.QueryCallbackWithArgs = ( + queryBuilder: Knex.QueryBuilder, + isOss: boolean, + ) => { + if (isOss) { + return queryBuilder.andWhere('project', '=', 'default'); + } + return queryBuilder; + }; + rowToFeature(row: FeaturesTable): FeatureToggle { if (!row) { throw new NotFoundError('No feature flag found'); diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts index f1718c149166..fb03d5ec8a1a 100644 --- a/src/lib/features/instance-stats/createInstanceStatsService.ts +++ b/src/lib/features/instance-stats/createInstanceStatsService.ts @@ -58,13 +58,8 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => { flagResolver, ); const userStore = new UserStore(db, getLogger, flagResolver); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const projectStore = new ProjectStore(db, eventBus, config); + const environmentStore = new EnvironmentStore(db, eventBus, config); const strategyStore = new StrategyStore(db, getLogger); const contextFieldStore = new ContextFieldStore( db, diff --git a/src/lib/features/project-environments/createEnvironmentService.ts b/src/lib/features/project-environments/createEnvironmentService.ts index 9cbcb478c193..9a1dfbd6c626 100644 --- a/src/lib/features/project-environments/createEnvironmentService.ts +++ b/src/lib/features/project-environments/createEnvironmentService.ts @@ -21,21 +21,16 @@ export const createEnvironmentService = const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, - ); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, + config, ); + const projectStore = new ProjectStore(db, eventBus, config); const featureStrategiesStore = new FeatureStrategiesStore( db, eventBus, getLogger, flagResolver, ); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const eventService = createEventsService(db, config); return new EnvironmentService( { diff --git a/src/lib/features/project-environments/environment-store.ts b/src/lib/features/project-environments/environment-store.ts index 6838d6eb4a81..3da9522b0018 100644 --- a/src/lib/features/project-environments/environment-store.ts +++ b/src/lib/features/project-environments/environment-store.ts @@ -1,6 +1,6 @@ import type EventEmitter from 'events'; import type { Db } from '../../db/db'; -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; import type { @@ -12,6 +12,7 @@ import NotFoundError from '../../error/notfound-error'; import type { IEnvironmentStore } from './environment-store-type'; import { snakeCaseKeys } from '../../util/snakeCase'; import type { CreateFeatureStrategySchema } from '../../openapi'; +import type { IUnleashConfig } from '../../types'; interface IEnvironmentsTable { name: string; @@ -104,11 +105,18 @@ export default class EnvironmentStore implements IEnvironmentStore { private db: Db; + private isOss: boolean; + private timer: (string) => any; - constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) { + constructor( + db: Db, + eventBus: EventEmitter, + { getLogger, isOss }: Pick<IUnleashConfig, 'getLogger' | 'isOss'>, + ) { this.db = db; this.logger = getLogger('db/environment-store.ts'); + this.isOss = isOss; this.timer = (action) => metricsHelper.wrapTimer(eventBus, DB_TIME, { store: 'environment', @@ -148,9 +156,15 @@ export default class EnvironmentStore implements IEnvironmentStore { async get(key: string): Promise<IEnvironment> { const stopTimer = this.timer('get'); - const row = await this.db<IEnvironmentsTable>(TABLE) - .where({ name: key }) - .first(); + let keyQuery = this.db<IEnvironmentsTable>(TABLE).where({ name: key }); + if (this.isOss) { + keyQuery = keyQuery.whereIn('name', [ + 'default', + 'development', + 'production', + ]); + } + const row = await keyQuery.first(); stopTimer(); if (row) { return mapRow(row); @@ -169,6 +183,9 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('name', ['default', 'development', 'production']); + } const rows = await qB; stopTimer(); return rows.map(mapRow); @@ -196,6 +213,9 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('name', ['default', 'development', 'production']); + } const rows = await qB; stopTimer(); return rows.map(mapRowWithCounts); @@ -230,6 +250,13 @@ export default class EnvironmentStore implements IEnvironmentStore { if (query) { qB = qB.where(query); } + if (this.isOss) { + qB = qB.whereIn('environments.name', [ + 'default', + 'production', + 'development', + ]); + } const rows = await qB; stopTimer(); diff --git a/src/lib/features/project-insights/createProjectInsightsService.ts b/src/lib/features/project-insights/createProjectInsightsService.ts index 68b46ac3941f..f15c1d61ad07 100644 --- a/src/lib/features/project-insights/createProjectInsightsService.ts +++ b/src/lib/features/project-insights/createProjectInsightsService.ts @@ -16,12 +16,7 @@ export const createProjectInsightsService = ( config: IUnleashConfig, ): ProjectInsightsService => { const { eventBus, getLogger, flagResolver } = config; - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const featureToggleStore = new FeatureToggleStore( db, eventBus, diff --git a/src/lib/features/project-status/createProjectStatusService.ts b/src/lib/features/project-status/createProjectStatusService.ts index c8694775403d..165c641ce8cd 100644 --- a/src/lib/features/project-status/createProjectStatusService.ts +++ b/src/lib/features/project-status/createProjectStatusService.ts @@ -24,12 +24,7 @@ export const createProjectStatusService = ( config: IUnleashConfig, ): ProjectStatusService => { const eventStore = new EventStore(db, config.getLogger); - const projectStore = new ProjectStore( - db, - config.eventBus, - config.getLogger, - config.flagResolver, - ); + const projectStore = new ProjectStore(db, config.eventBus, config); const apiTokenStore = new ApiTokenStore( db, config.eventBus, diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index d4a42888eba6..a753ca34224f 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -63,12 +63,7 @@ export const createProjectService = ( ): ProjectService => { const { eventBus, getLogger, flagResolver } = config; const eventStore = new EventStore(db, getLogger); - const projectStore = new ProjectStore( - db, - eventBus, - getLogger, - flagResolver, - ); + const projectStore = new ProjectStore(db, eventBus, config); const projectOwnersReadModel = new ProjectOwnersReadModel(db); const projectFlagCreatorsReadModel = new ProjectFlagCreatorsReadModel(db); const groupStore = new GroupStore(db); @@ -79,11 +74,11 @@ export const createProjectService = ( flagResolver, ); const accountStore = new AccountStore(db, getLogger); - const environmentStore = new EnvironmentStore(db, eventBus, getLogger); + const environmentStore = new EnvironmentStore(db, eventBus, config); const featureEnvironmentStore = new FeatureEnvironmentStore( db, eventBus, - getLogger, + config, ); const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger); const accessService: AccessService = createAccessService(db, config); diff --git a/src/lib/features/project/project-store.ts b/src/lib/features/project/project-store.ts index 5df93193c63e..5011159825c4 100644 --- a/src/lib/features/project/project-store.ts +++ b/src/lib/features/project/project-store.ts @@ -1,4 +1,4 @@ -import type { Logger, LogProvider } from '../../logger'; +import type { Logger } from '../../logger'; import NotFoundError from '../../error/notfound-error'; import type { @@ -8,6 +8,7 @@ import type { IProjectApplication, IProjectApplications, IProjectUpdate, + IUnleashConfig, ProjectMode, } from '../../types'; import type { @@ -72,11 +73,16 @@ class ProjectStore implements IProjectStore { private timer: Function; + private isOss: boolean; + constructor( db: Db, eventBus: EventEmitter, - getLogger: LogProvider, - flagResolver: IFlagResolver, + { + getLogger, + flagResolver, + isOss, + }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>, ) { this.db = db; this.logger = getLogger('project-store.ts'); @@ -86,6 +92,7 @@ class ProjectStore implements IProjectStore { action, }); this.flagResolver = flagResolver; + this.isOss = isOss; } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -122,6 +129,9 @@ class ProjectStore implements IProjectStore { .orderBy('name', 'asc'); projects = projects.where(`${TABLE}.archived_at`, null); + if (this.isOss) { + projects = projects.where('id', 'default'); + } const rows = await projects; diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index 792cb22dc874..b4919ad34152 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -143,6 +143,7 @@ export interface IUnleashOptions { metricsRateLimiting?: Partial<IMetricsRateLimiting>; dailyMetricsStorageDays?: number; rateLimiting?: Partial<IRateLimiting>; + isOss?: boolean; resourceLimits?: Partial< Pick< ResourceLimitsSchema, @@ -273,6 +274,7 @@ export interface IUnleashConfig { publicFolder?: string; disableScheduler?: boolean; isEnterprise: boolean; + isOss: boolean; rateLimiting: IRateLimiting; feedbackUriPath?: string; openAIAPIKey?: string; diff --git a/src/test/e2e/api/admin/environment-oss.e2e.test.ts b/src/test/e2e/api/admin/environment-oss.e2e.test.ts new file mode 100644 index 000000000000..4de671b7d853 --- /dev/null +++ b/src/test/e2e/api/admin/environment-oss.e2e.test.ts @@ -0,0 +1,68 @@ +import { + type IUnleashTest, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; +import dbInit, { type ITestDb } from '../../helpers/database-init'; +import getLogger from '../../../fixtures/no-logger'; + +let app: IUnleashTest; +let db: ITestDb; + +beforeAll(async () => { + db = await dbInit('environment_api_is_oss_serial', getLogger, { + isOss: true, + }); + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + isOss: true, + }, + db.rawDatabase, + ); + await db.stores.environmentStore.create({ + name: 'development', + type: 'development', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'production', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment2', + type: 'production', + enabled: true, + }); + await db.stores.environmentStore.create({ + name: 'customenvironment3', + type: 'production', + enabled: true, + }); +}); + +afterAll(async () => { + await app.destroy(); + await db.destroy(); +}); + +test('querying environments in OSS only returns environments that are included in oss', async () => { + await app.request + .get('/api/admin/environments') + .expect(200) + .expect((res) => { + expect(res.body.environments).toHaveLength(3); + const names = res.body.environments.map((env) => env.name); + expect(names).toEqual(['default', 'development', 'production']); + }); +}); diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts new file mode 100644 index 000000000000..c54c8308980d --- /dev/null +++ b/src/test/e2e/api/client/feature-oss.e2e.test.ts @@ -0,0 +1,82 @@ +import getLogger from '../../../fixtures/no-logger'; +import { + type IUnleashTest, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; +import dbInit from '../../helpers/database-init'; +import type { ITestDb } from '../../helpers/database-init'; +import type { IAuditUser, IUser } from '../../../../lib/types'; + +let app: IUnleashTest; +let db: ITestDb; +let testUser: IUser; +const auditUser = { + username: 'audituser', + id: -42, + ip: 'localhost', +} as IAuditUser; + +let userIndex = 0; +const createUser = async (role?: number) => { + const name = `User ${userIndex}`; + const email = `user-${userIndex}@getunleash.io`; + userIndex++; + + const { userStore } = db.stores; + return userStore.insert({ name, email }); +}; + +beforeAll(async () => { + db = await dbInit('feature_api_client_is_oss', getLogger, { isOss: true }); + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + isOss: true, + }, + db.rawDatabase, + ); + testUser = await createUser(); + await app.services.projectService.createProject( + { id: 'secondproject', name: 'Second project not returned when oss' }, + testUser, + auditUser, + ); + await app.services.featureToggleService.createFeatureToggle( + 'secondproject', + { + name: 'my.feature.toggle', + }, + auditUser, + true, + ); + await app.services.featureToggleService.createFeatureToggle( + 'default', + { + name: 'my.default.toggle', + }, + auditUser, + true, + ); +}); +afterAll(async () => { + await app.destroy(); + await db.destroy(); +}); + +describe('OSS downgrade', () => { + test('features created in projects other than default is not visible in client endpoint', async () => { + return app.request + .get('/api/client/features') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.features).toHaveLength(1); + expect(res.body.features[0].name).toBe('my.default.toggle'); + }); + }); +}); From 1fdc542c7c83916451158eddbc67e5338a4c48a8 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Fri, 29 Nov 2024 14:24:13 +0100 Subject: [PATCH 2/9] fix: added separate method for resolving isOss flag to be able to test behaviour when NODE_ENV !== test --- src/lib/create-config.test.ts | 21 ++++++++++++++++++++- src/lib/create-config.ts | 21 ++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts index e69eb7b500df..06646635e2a1 100644 --- a/src/lib/create-config.test.ts +++ b/src/lib/create-config.test.ts @@ -1,4 +1,4 @@ -import { createConfig } from './create-config'; +import { createConfig, resolveIsOss } from './create-config'; import { ApiTokenType } from './types/models/api-token'; test('should create default config', async () => { @@ -498,3 +498,22 @@ test('Config with enterpriseVersion set and not pro environment should set isEnt }); expect(config.isEnterprise).toBe(true); }); + +describe('isOSS', () => { + test('Config with pro environment should set isOss to false regardless of pro casing', async () => { + const isOss = resolveIsOss(false, false, 'Pro'); + expect(isOss).toBe(false); + }); + test('Config with enterpriseVersion set should set isOss to false', async () => { + const isOss = resolveIsOss(true, false, 'Enterprise'); + expect(isOss).toBe(false); + }); + test('Config with no enterprise version and any other environment than pro should have isOss as true', async () => { + const isOss = resolveIsOss(false, false, 'my oss environment'); + expect(isOss).toBe(true); + }); + test('Config with enterprise false and isOss option set to false should return false in test mode', async () => { + const isOss = resolveIsOss(false, false, 'my environment', true); + expect(isOss).toBe(false); + }); +}); diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 015e7a9973ac..25194c47217a 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -496,6 +496,17 @@ const parseFrontendApiOrigins = (options: IUnleashOptions): string[] => { return frontendApiOrigins; }; +export function resolveIsOss( + isEnterprise: boolean, + isOssOption?: boolean, + uiEnvironment?: string, + testEnvironmentActive: boolean = false, +): boolean { + return testEnvironmentActive + ? (isOssOption ?? false) + : !isEnterprise && uiEnvironment?.toLowerCase() !== 'pro'; +} + export function createConfig(options: IUnleashOptions): IUnleashConfig { let extraDbOptions = {}; @@ -621,9 +632,12 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { ui.environment?.toLowerCase() !== 'pro'; const isTest = process.env.NODE_ENV === 'test'; - const isOss = isTest - ? options.isOss || false - : !isEnterprise && ui.environment !== 'pro'; + const isOss = resolveIsOss( + isEnterprise, + options.isOss, + ui.environment, + isTest, + ); const metricsRateLimiting = loadMetricsRateLimitingConfig(options); const rateLimiting = loadRateLimitingConfig(options); @@ -776,5 +790,6 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { module.exports = { createConfig, + resolveIsOss, authTypeFromString, }; From 8e8bfa0e882f0cdec93b19e7e405791b29ca7c8d Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Fri, 29 Nov 2024 14:27:48 +0100 Subject: [PATCH 3/9] added one more test --- src/lib/create-config.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/create-config.test.ts b/src/lib/create-config.test.ts index 06646635e2a1..940eedd1e37b 100644 --- a/src/lib/create-config.test.ts +++ b/src/lib/create-config.test.ts @@ -503,6 +503,10 @@ describe('isOSS', () => { test('Config with pro environment should set isOss to false regardless of pro casing', async () => { const isOss = resolveIsOss(false, false, 'Pro'); expect(isOss).toBe(false); + const lowerCase = resolveIsOss(false, false, 'pro'); + expect(lowerCase).toBe(false); + const strangeCase = resolveIsOss(false, false, 'PrO'); + expect(strangeCase).toBe(false); }); test('Config with enterpriseVersion set should set isOss to false', async () => { const isOss = resolveIsOss(true, false, 'Enterprise'); @@ -516,4 +520,14 @@ describe('isOSS', () => { const isOss = resolveIsOss(false, false, 'my environment', true); expect(isOss).toBe(false); }); + test('Config with isOss option set to true should return true when test environment is active', async () => { + let isOss = resolveIsOss(false, true, 'Pro', true); + expect(isOss).toBe(true); + + isOss = resolveIsOss(true, true, 'Pro', true); + expect(isOss).toBe(true); + + isOss = resolveIsOss(false, true, 'some environment', true); + expect(isOss).toBe(true); + }); }); From 59126e8cc3bb657fd4c2276768f542b905121fd1 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Thu, 19 Dec 2024 10:58:18 +0100 Subject: [PATCH 4/9] make sure to not filter client features even if oss --- .../client-feature-toggle-store.ts | 4 ---- .../features/feature-toggle/feature-toggle-store.ts | 10 ---------- src/test/e2e/api/client/feature-oss.e2e.test.ts | 13 ------------- 3 files changed, 27 deletions(-) diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts index 400ff737d507..0a674fe5a688 100644 --- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts +++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts @@ -109,10 +109,6 @@ export default class FeatureToggleClientStore let query = this.db('features') .modify(FeatureToggleStore.filterByArchived, archived) - .modify( - FeatureToggleStore.filterByProjectsAccessibleByOss, - this.isOss, - ) .leftJoin( this.db('feature_strategies') .select('*') diff --git a/src/lib/features/feature-toggle/feature-toggle-store.ts b/src/lib/features/feature-toggle/feature-toggle-store.ts index 861d1af92c5e..fe412fb9986a 100644 --- a/src/lib/features/feature-toggle/feature-toggle-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-store.ts @@ -439,16 +439,6 @@ export default class FeatureToggleStore implements IFeatureToggleStore { : queryBuilder.whereNull('archived_at'); }; - static filterByProjectsAccessibleByOss: Knex.QueryCallbackWithArgs = ( - queryBuilder: Knex.QueryBuilder, - isOss: boolean, - ) => { - if (isOss) { - return queryBuilder.andWhere('project', '=', 'default'); - } - return queryBuilder; - }; - rowToFeature(row: FeaturesTable): FeatureToggle { if (!row) { throw new NotFoundError('No feature flag found'); diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts index c54c8308980d..ce20b6495ff1 100644 --- a/src/test/e2e/api/client/feature-oss.e2e.test.ts +++ b/src/test/e2e/api/client/feature-oss.e2e.test.ts @@ -67,16 +67,3 @@ afterAll(async () => { await app.destroy(); await db.destroy(); }); - -describe('OSS downgrade', () => { - test('features created in projects other than default is not visible in client endpoint', async () => { - return app.request - .get('/api/client/features') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - expect(res.body.features).toHaveLength(1); - expect(res.body.features[0].name).toBe('my.default.toggle'); - }); - }); -}); From 29a2cc06f8b2cb383873f6660a18f69a536fc3c8 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Thu, 19 Dec 2024 11:03:59 +0100 Subject: [PATCH 5/9] Don't need isOss for FeatureToggleClientStore --- .../client-feature-toggles/client-feature-toggle-store.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts index 0a674fe5a688..435dae0d74f7 100644 --- a/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts +++ b/src/lib/features/client-feature-toggles/client-feature-toggle-store.ts @@ -47,16 +47,13 @@ export default class FeatureToggleClientStore private flagResolver: IFlagResolver; - private readonly isOss: boolean; - constructor( db: Db, eventBus: EventEmitter, { getLogger, flagResolver, - isOss, - }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver' | 'isOss'>, + }: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>, ) { this.db = db; this.logger = getLogger('feature-toggle-client-store.ts'); @@ -66,7 +63,6 @@ export default class FeatureToggleClientStore action, }); this.flagResolver = flagResolver; - this.isOss = isOss; } private async getAll({ From de5f9bed658a88fae819e830b9b0c0a7fed9b91f Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Thu, 19 Dec 2024 11:46:21 +0100 Subject: [PATCH 6/9] Don't need isOss for FeatureToggleClientStore --- src/lib/db/index.ts | 6 +-- .../onboarding/createOnboardingService.ts | 6 +-- .../createPersonalDashboardService.ts | 2 +- .../project/createProjectReadModel.ts | 9 ++--- .../features/project/createProjectService.ts | 6 +-- .../features/project/project-read-model.ts | 39 +++++++++++++------ 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 3722d91d46fd..7afc92d54939 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -178,11 +178,7 @@ export const createStores = ( largestResourcesReadModel: new LargestResourcesReadModel(db), integrationEventsStore: new IntegrationEventsStore(db, { eventBus }), featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db), - projectReadModel: createProjectReadModel( - db, - eventBus, - config.flagResolver, - ), + projectReadModel: createProjectReadModel(db, config), userUnsubscribeStore: new UserUnsubscribeStore(db), userSubscriptionsReadModel: new UserSubscriptionsReadModel(db), }; diff --git a/src/lib/features/onboarding/createOnboardingService.ts b/src/lib/features/onboarding/createOnboardingService.ts index bd206182df5e..3e03fe71dc87 100644 --- a/src/lib/features/onboarding/createOnboardingService.ts +++ b/src/lib/features/onboarding/createOnboardingService.ts @@ -13,11 +13,7 @@ export const createOnboardingService = (db: Db): OnboardingService => { const { eventBus, flagResolver, getLogger } = config; const onboardingStore = new OnboardingStore(db); - const projectReadModel = new ProjectReadModel( - db, - eventBus, - flagResolver, - ); + const projectReadModel = new ProjectReadModel(db, config); const userStore = new UserStore(db, getLogger, flagResolver); const onboardingService = new OnboardingService( { diff --git a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts index 8f5955f970fd..dd66fab10359 100644 --- a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts +++ b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts @@ -27,7 +27,7 @@ export const createPersonalDashboardService = ( return new PersonalDashboardService( new PersonalDashboardReadModel(db), new ProjectOwnersReadModel(db), - new ProjectReadModel(db, config.eventBus, config.flagResolver), + new ProjectReadModel(db, config), new OnboardingReadModel(db), new EventStore(db, config.getLogger), new FeatureEventFormatterMd({ diff --git a/src/lib/features/project/createProjectReadModel.ts b/src/lib/features/project/createProjectReadModel.ts index 2a589475f6da..c4c10fa71830 100644 --- a/src/lib/features/project/createProjectReadModel.ts +++ b/src/lib/features/project/createProjectReadModel.ts @@ -1,16 +1,13 @@ -import type EventEmitter from 'events'; -import type { Db } from '../../server-impl'; +import type { Db, IUnleashConfig } from '../../server-impl'; import type { IProjectReadModel } from './project-read-model-type'; -import type { IFlagResolver } from '../../types'; import { ProjectReadModel } from './project-read-model'; import { FakeProjectReadModel } from './fake-project-read-model'; export const createProjectReadModel = ( db: Db, - eventBus: EventEmitter, - flagResolver: IFlagResolver, + config: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>, ): IProjectReadModel => { - return new ProjectReadModel(db, eventBus, flagResolver); + return new ProjectReadModel(db, config); }; export const createFakeProjectReadModel = (): IProjectReadModel => { diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index a753ca34224f..bc69e0e59a15 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -123,11 +123,7 @@ export const createProjectService = ( eventService, ); - const projectReadModel = createProjectReadModel( - db, - eventBus, - config.flagResolver, - ); + const projectReadModel = createProjectReadModel(db, config); const onboardingReadModel = createOnboardingReadModel(db); diff --git a/src/lib/features/project/project-read-model.ts b/src/lib/features/project/project-read-model.ts index 37190b8e068c..ef86bcb71eeb 100644 --- a/src/lib/features/project/project-read-model.ts +++ b/src/lib/features/project/project-read-model.ts @@ -1,4 +1,4 @@ -import type { IFlagResolver } from '../../types'; +import type { IFlagResolver, IUnleashConfig } from '../../types'; import { Knex } from 'knex'; import type { Db } from '../../db/db'; import type { @@ -8,7 +8,6 @@ import type { } from './project-read-model-type'; import type { IProjectQuery, IProjectsQuery } from './project-store-type'; import metricsHelper from '../../util/metrics-helper'; -import type EventEmitter from 'events'; import type { IProjectMembersCount } from './project-store'; import Raw = Knex.Raw; @@ -52,7 +51,16 @@ export class ProjectReadModel implements IProjectReadModel { private flagResolver: IFlagResolver; - constructor(db: Db, eventBus: EventEmitter, flagResolver: IFlagResolver) { + private readonly isOss: boolean = false; + + constructor( + db: Db, + { + eventBus, + flagResolver, + isOss, + }: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>, + ) { this.db = db; this.timer = (action) => metricsHelper.wrapTimer(eventBus, DB_TIME, { @@ -60,6 +68,7 @@ export class ProjectReadModel implements IProjectReadModel { action, }); this.flagResolver = flagResolver; + this.isOss = isOss; } async getFeatureProject( @@ -118,11 +127,15 @@ export class ProjectReadModel implements IProjectReadModel { projects = projects.where(`${TABLE}.archived_at`, null); } - if (query?.id) { - projects = projects.where(`${TABLE}.id`, query.id); - } - if (query?.ids) { - projects = projects.whereIn(`${TABLE}.id`, query.ids); + if (this.isOss) { + projects = projects.where(`${TABLE}.id`, 'default'); + } else { + if (query?.id) { + projects = projects.where(`${TABLE}.id`, query.id); + } + if (query?.ids) { + projects = projects.whereIn(`${TABLE}.id`, query.ids); + } } let selectColumns = [ @@ -191,7 +204,9 @@ export class ProjectReadModel implements IProjectReadModel { projects = projects.where(`${TABLE}.archived_at`, null); } - if (query?.id) { + if (this.isOss) { + projects = projects.where(`${TABLE}.id`, 'default'); + } else if (query?.id) { projects = projects.where(`${TABLE}.id`, query.id); } @@ -294,14 +309,16 @@ export class ProjectReadModel implements IProjectReadModel { } async getProjectsFavoritedByUser(userId: number): Promise<string[]> { - const favoritedProjects = await this.db + const favoritedProjects = this.db .select('favorite_projects.project') .from('favorite_projects') .leftJoin('projects', 'favorite_projects.project', 'projects.id') .where('favorite_projects.user_id', userId) .andWhere('projects.archived_at', null) .pluck('project'); - + if (this.isOss) { + favoritedProjects.where('projects.id', 'default'); + } return favoritedProjects; } } From 3468161278b80f3972ee128f097347284d273b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= <gaston@getunleash.io> Date: Thu, 19 Dec 2024 12:56:09 +0100 Subject: [PATCH 7/9] chore: restrict on RBAC middleware (#9000) --- src/lib/middleware/rbac-middleware.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/lib/middleware/rbac-middleware.ts b/src/lib/middleware/rbac-middleware.ts index 33ffd108b5a2..88e558f0e98e 100644 --- a/src/lib/middleware/rbac-middleware.ts +++ b/src/lib/middleware/rbac-middleware.ts @@ -33,7 +33,7 @@ export function findParam( } const rbacMiddleware = ( - config: Pick<IUnleashConfig, 'getLogger'>, + config: Pick<IUnleashConfig, 'getLogger' | 'isOss'>, { featureToggleStore, segmentStore, @@ -98,6 +98,24 @@ const rbacMiddleware = ( ) { projectId = 'default'; } + if (config.isOss) { + if (projectId !== undefined && projectId !== 'default') { + logger.error( + 'OSS is only allowed to work with default project.', + ); + return false; + } + const ossEnvs = ['default', 'development', 'production']; + if ( + environment !== undefined && + !ossEnvs.includes(environment) + ) { + logger.error( + `OSS is only allowed to work with ${ossEnvs} environments.`, + ); + return false; + } + } // DELETE segment does not include information about the segment's project // This is needed to check if the user has the right permissions on a project level From e46d659949e51e411742da6fd964b1709d0da155 Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Thu, 19 Dec 2024 13:03:58 +0100 Subject: [PATCH 8/9] Revert changes to readModel --- src/lib/db/index.ts | 6 ++- .../onboarding/createOnboardingService.ts | 6 ++- .../createPersonalDashboardService.ts | 2 +- .../project/createProjectReadModel.ts | 9 +++-- .../features/project/createProjectService.ts | 6 ++- .../features/project/project-read-model.ts | 39 ++++++------------- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 7afc92d54939..3722d91d46fd 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -178,7 +178,11 @@ export const createStores = ( largestResourcesReadModel: new LargestResourcesReadModel(db), integrationEventsStore: new IntegrationEventsStore(db, { eventBus }), featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db), - projectReadModel: createProjectReadModel(db, config), + projectReadModel: createProjectReadModel( + db, + eventBus, + config.flagResolver, + ), userUnsubscribeStore: new UserUnsubscribeStore(db), userSubscriptionsReadModel: new UserSubscriptionsReadModel(db), }; diff --git a/src/lib/features/onboarding/createOnboardingService.ts b/src/lib/features/onboarding/createOnboardingService.ts index 3e03fe71dc87..bd206182df5e 100644 --- a/src/lib/features/onboarding/createOnboardingService.ts +++ b/src/lib/features/onboarding/createOnboardingService.ts @@ -13,7 +13,11 @@ export const createOnboardingService = (db: Db): OnboardingService => { const { eventBus, flagResolver, getLogger } = config; const onboardingStore = new OnboardingStore(db); - const projectReadModel = new ProjectReadModel(db, config); + const projectReadModel = new ProjectReadModel( + db, + eventBus, + flagResolver, + ); const userStore = new UserStore(db, getLogger, flagResolver); const onboardingService = new OnboardingService( { diff --git a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts index dd66fab10359..8f5955f970fd 100644 --- a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts +++ b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts @@ -27,7 +27,7 @@ export const createPersonalDashboardService = ( return new PersonalDashboardService( new PersonalDashboardReadModel(db), new ProjectOwnersReadModel(db), - new ProjectReadModel(db, config), + new ProjectReadModel(db, config.eventBus, config.flagResolver), new OnboardingReadModel(db), new EventStore(db, config.getLogger), new FeatureEventFormatterMd({ diff --git a/src/lib/features/project/createProjectReadModel.ts b/src/lib/features/project/createProjectReadModel.ts index c4c10fa71830..2a589475f6da 100644 --- a/src/lib/features/project/createProjectReadModel.ts +++ b/src/lib/features/project/createProjectReadModel.ts @@ -1,13 +1,16 @@ -import type { Db, IUnleashConfig } from '../../server-impl'; +import type EventEmitter from 'events'; +import type { Db } from '../../server-impl'; import type { IProjectReadModel } from './project-read-model-type'; +import type { IFlagResolver } from '../../types'; import { ProjectReadModel } from './project-read-model'; import { FakeProjectReadModel } from './fake-project-read-model'; export const createProjectReadModel = ( db: Db, - config: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>, + eventBus: EventEmitter, + flagResolver: IFlagResolver, ): IProjectReadModel => { - return new ProjectReadModel(db, config); + return new ProjectReadModel(db, eventBus, flagResolver); }; export const createFakeProjectReadModel = (): IProjectReadModel => { diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index bc69e0e59a15..a753ca34224f 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -123,7 +123,11 @@ export const createProjectService = ( eventService, ); - const projectReadModel = createProjectReadModel(db, config); + const projectReadModel = createProjectReadModel( + db, + eventBus, + config.flagResolver, + ); const onboardingReadModel = createOnboardingReadModel(db); diff --git a/src/lib/features/project/project-read-model.ts b/src/lib/features/project/project-read-model.ts index ef86bcb71eeb..37190b8e068c 100644 --- a/src/lib/features/project/project-read-model.ts +++ b/src/lib/features/project/project-read-model.ts @@ -1,4 +1,4 @@ -import type { IFlagResolver, IUnleashConfig } from '../../types'; +import type { IFlagResolver } from '../../types'; import { Knex } from 'knex'; import type { Db } from '../../db/db'; import type { @@ -8,6 +8,7 @@ import type { } from './project-read-model-type'; import type { IProjectQuery, IProjectsQuery } from './project-store-type'; import metricsHelper from '../../util/metrics-helper'; +import type EventEmitter from 'events'; import type { IProjectMembersCount } from './project-store'; import Raw = Knex.Raw; @@ -51,16 +52,7 @@ export class ProjectReadModel implements IProjectReadModel { private flagResolver: IFlagResolver; - private readonly isOss: boolean = false; - - constructor( - db: Db, - { - eventBus, - flagResolver, - isOss, - }: Pick<IUnleashConfig, 'eventBus' | 'flagResolver' | 'isOss'>, - ) { + constructor(db: Db, eventBus: EventEmitter, flagResolver: IFlagResolver) { this.db = db; this.timer = (action) => metricsHelper.wrapTimer(eventBus, DB_TIME, { @@ -68,7 +60,6 @@ export class ProjectReadModel implements IProjectReadModel { action, }); this.flagResolver = flagResolver; - this.isOss = isOss; } async getFeatureProject( @@ -127,15 +118,11 @@ export class ProjectReadModel implements IProjectReadModel { projects = projects.where(`${TABLE}.archived_at`, null); } - if (this.isOss) { - projects = projects.where(`${TABLE}.id`, 'default'); - } else { - if (query?.id) { - projects = projects.where(`${TABLE}.id`, query.id); - } - if (query?.ids) { - projects = projects.whereIn(`${TABLE}.id`, query.ids); - } + if (query?.id) { + projects = projects.where(`${TABLE}.id`, query.id); + } + if (query?.ids) { + projects = projects.whereIn(`${TABLE}.id`, query.ids); } let selectColumns = [ @@ -204,9 +191,7 @@ export class ProjectReadModel implements IProjectReadModel { projects = projects.where(`${TABLE}.archived_at`, null); } - if (this.isOss) { - projects = projects.where(`${TABLE}.id`, 'default'); - } else if (query?.id) { + if (query?.id) { projects = projects.where(`${TABLE}.id`, query.id); } @@ -309,16 +294,14 @@ export class ProjectReadModel implements IProjectReadModel { } async getProjectsFavoritedByUser(userId: number): Promise<string[]> { - const favoritedProjects = this.db + const favoritedProjects = await this.db .select('favorite_projects.project') .from('favorite_projects') .leftJoin('projects', 'favorite_projects.project', 'projects.id') .where('favorite_projects.user_id', userId) .andWhere('projects.archived_at', null) .pluck('project'); - if (this.isOss) { - favoritedProjects.where('projects.id', 'default'); - } + return favoritedProjects; } } From 713f5317225bb52958de5453bb89c009c3df954f Mon Sep 17 00:00:00 2001 From: Christopher Kolstad <chriswk@getunleash.io> Date: Thu, 19 Dec 2024 13:09:10 +0100 Subject: [PATCH 9/9] Remove non-running test --- .../e2e/api/client/feature-oss.e2e.test.ts | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 src/test/e2e/api/client/feature-oss.e2e.test.ts diff --git a/src/test/e2e/api/client/feature-oss.e2e.test.ts b/src/test/e2e/api/client/feature-oss.e2e.test.ts deleted file mode 100644 index ce20b6495ff1..000000000000 --- a/src/test/e2e/api/client/feature-oss.e2e.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import getLogger from '../../../fixtures/no-logger'; -import { - type IUnleashTest, - setupAppWithCustomConfig, -} from '../../helpers/test-helper'; -import dbInit from '../../helpers/database-init'; -import type { ITestDb } from '../../helpers/database-init'; -import type { IAuditUser, IUser } from '../../../../lib/types'; - -let app: IUnleashTest; -let db: ITestDb; -let testUser: IUser; -const auditUser = { - username: 'audituser', - id: -42, - ip: 'localhost', -} as IAuditUser; - -let userIndex = 0; -const createUser = async (role?: number) => { - const name = `User ${userIndex}`; - const email = `user-${userIndex}@getunleash.io`; - userIndex++; - - const { userStore } = db.stores; - return userStore.insert({ name, email }); -}; - -beforeAll(async () => { - db = await dbInit('feature_api_client_is_oss', getLogger, { isOss: true }); - app = await setupAppWithCustomConfig( - db.stores, - { - experimental: { - flags: { - strictSchemaValidation: true, - }, - }, - isOss: true, - }, - db.rawDatabase, - ); - testUser = await createUser(); - await app.services.projectService.createProject( - { id: 'secondproject', name: 'Second project not returned when oss' }, - testUser, - auditUser, - ); - await app.services.featureToggleService.createFeatureToggle( - 'secondproject', - { - name: 'my.feature.toggle', - }, - auditUser, - true, - ); - await app.services.featureToggleService.createFeatureToggle( - 'default', - { - name: 'my.default.toggle', - }, - auditUser, - true, - ); -}); -afterAll(async () => { - await app.destroy(); - await db.destroy(); -});