From f9409fc0e6f756da7e0646e56013e29d9b55829b Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 19 May 2023 08:44:17 +0200 Subject: [PATCH] feat: transactional bulk update (#3806) --- src/lib/routes/admin-api/index.ts | 2 +- src/lib/routes/admin-api/project/index.ts | 13 +++- .../admin-api/project/project-features.ts | 61 +++++++++++++------ src/lib/services/index.ts | 4 ++ src/lib/types/services.ts | 3 + 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/lib/routes/admin-api/index.ts b/src/lib/routes/admin-api/index.ts index b6ade77077..d4ea29dfc6 100644 --- a/src/lib/routes/admin-api/index.ts +++ b/src/lib/routes/admin-api/index.ts @@ -107,7 +107,7 @@ class AdminApi extends Controller { '/feedback', new UserFeedbackController(config, services).router, ); - this.app.use('/projects', new ProjectApi(config, services).router); + this.app.use('/projects', new ProjectApi(config, services, db).router); this.app.use( '/environments', new EnvironmentsController(config, services).router, diff --git a/src/lib/routes/admin-api/project/index.ts b/src/lib/routes/admin-api/project/index.ts index 2be09bb099..845f6c081d 100644 --- a/src/lib/routes/admin-api/project/index.ts +++ b/src/lib/routes/admin-api/project/index.ts @@ -24,6 +24,8 @@ import { OpenApiService, SettingService } from '../../../services'; import { IAuthRequest } from '../../unleash-types'; import { ProjectApiTokenController } from './api-token'; import ProjectArchiveController from './project-archive'; +import { createKnexTransactionStarter } from '../../../db/transaction'; +import { Db } from '../../../db/db'; export default class ProjectApi extends Controller { private projectService: ProjectService; @@ -32,7 +34,7 @@ export default class ProjectApi extends Controller { private openApiService: OpenApiService; - constructor(config: IUnleashConfig, services: IUnleashServices) { + constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) { super(config); this.projectService = services.projectService; this.openApiService = services.openApiService; @@ -70,7 +72,14 @@ export default class ProjectApi extends Controller { ], }); - this.use('/', new ProjectFeaturesController(config, services).router); + this.use( + '/', + new ProjectFeaturesController( + config, + services, + createKnexTransactionStarter(db), + ).router, + ); this.use('/', new EnvironmentsController(config, services).router); this.use('/', new ProjectHealthReport(config, services).router); this.use('/', new VariantsController(config, services).router); diff --git a/src/lib/routes/admin-api/project/project-features.ts b/src/lib/routes/admin-api/project/project-features.ts index d6bda03872..ef4e4c005d 100644 --- a/src/lib/routes/admin-api/project/project-features.ts +++ b/src/lib/routes/admin-api/project/project-features.ts @@ -42,6 +42,10 @@ import { import { OpenApiService, FeatureToggleService } from '../../../services'; import { querySchema } from '../../../schema/feature-schema'; import { BatchStaleSchema } from '../../../openapi/spec/batch-stale-schema'; +import { + TransactionCreator, + UnleashTransaction, +} from '../../../db/transaction'; interface FeatureStrategyParams { projectId: string; @@ -90,24 +94,41 @@ const PATH_STRATEGY = `${PATH_STRATEGIES}/:strategyId`; type ProjectFeaturesServices = Pick< IUnleashServices, - 'featureToggleServiceV2' | 'projectHealthService' | 'openApiService' + | 'featureToggleServiceV2' + | 'projectHealthService' + | 'openApiService' + | 'transactionalFeatureToggleService' >; export default class ProjectFeaturesController extends Controller { private featureService: FeatureToggleService; + private transactionalFeatureToggleService: ( + db: UnleashTransaction, + ) => FeatureToggleService; + private openApiService: OpenApiService; private flagResolver: IFlagResolver; private readonly logger: Logger; + private readonly startTransaction: TransactionCreator; + constructor( config: IUnleashConfig, - { featureToggleServiceV2, openApiService }: ProjectFeaturesServices, + { + featureToggleServiceV2, + openApiService, + transactionalFeatureToggleService, + }: ProjectFeaturesServices, + startTransaction: TransactionCreator, ) { super(config); this.featureService = featureToggleServiceV2; + this.transactionalFeatureToggleService = + transactionalFeatureToggleService; + this.startTransaction = startTransaction; this.openApiService = openApiService; this.flagResolver = config.flagResolver; this.logger = config.getLogger('/admin-api/project/features.ts'); @@ -727,14 +748,16 @@ export default class ProjectFeaturesController extends Controller { const { shouldActivateDisabledStrategies } = req.query; const { features } = req.body; - await this.featureService.bulkUpdateEnabled( - projectId, - features, - environment, - true, - extractUsername(req), - req.user, - shouldActivateDisabledStrategies === 'true', + await this.startTransaction(async (tx) => + this.transactionalFeatureToggleService(tx).bulkUpdateEnabled( + projectId, + features, + environment, + true, + extractUsername(req), + req.user, + shouldActivateDisabledStrategies === 'true', + ), ); res.status(200).end(); } @@ -752,14 +775,16 @@ export default class ProjectFeaturesController extends Controller { const { shouldActivateDisabledStrategies } = req.query; const { features } = req.body; - await this.featureService.bulkUpdateEnabled( - projectId, - features, - environment, - false, - extractUsername(req), - req.user, - shouldActivateDisabledStrategies === 'true', + await this.startTransaction(async (tx) => + this.transactionalFeatureToggleService(tx).bulkUpdateEnabled( + projectId, + features, + environment, + false, + extractUsername(req), + req.user, + shouldActivateDisabledStrategies === 'true', + ), ); res.status(200).end(); } diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 22c3be42c1..7688d1608d 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -57,6 +57,7 @@ import { createFakeChangeRequestAccessService, } from '../features/change-request-access-service/createChangeRequestAccessReadModel'; import ConfigurationRevisionService from '../features/feature-toggle/configuration-revision-service'; +import { createFeatureToggleService } from '../features'; // TODO: will be moved to scheduler feature directory export const scheduleServices = (services: IUnleashServices): void => { @@ -184,6 +185,8 @@ export const createServices = ( : createFakeExportImportTogglesService(config); const transactionalExportImportService = (txDb: Knex.Transaction) => createExportImportTogglesService(txDb, config); + const transactionalFeatureToggleService = (txDb: Knex.Transaction) => + createFeatureToggleService(txDb, config); const userSplashService = new UserSplashService(stores, config); const openApiService = new OpenApiService(config); const clientSpecService = new ClientSpecService(config); @@ -275,6 +278,7 @@ export const createServices = ( transactionalExportImportService, schedulerService, configurationRevisionService, + transactionalFeatureToggleService, }; }; diff --git a/src/lib/types/services.ts b/src/lib/types/services.ts index a21b93def8..3db0b22f53 100644 --- a/src/lib/types/services.ts +++ b/src/lib/types/services.ts @@ -91,4 +91,7 @@ export interface IUnleashServices { transactionalExportImportService: ( db: Knex.Transaction, ) => ExportImportService; + transactionalFeatureToggleService: ( + db: Knex.Transaction, + ) => FeatureToggleService; }