mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: extract UI config logic into its own service (#10704)
https://linear.app/unleash/issue/2-3921/extract-ui-config-logic-into-its-own-service Extracts UI config logic into its own service. This is the first step to accomplish resource limits and license key resources alignment.
This commit is contained in:
		
							parent
							
								
									b865ee44f3
								
							
						
					
					
						commit
						e46f8881d1
					
				| @ -8,7 +8,7 @@ import EventController from './event.js'; | ||||
| import PlaygroundController from '../../features/playground/playground.js'; | ||||
| import MetricsController from './metrics.js'; | ||||
| import UserController from './user/user.js'; | ||||
| import ConfigController from './config.js'; | ||||
| import UiConfigController from '../../ui-config/ui-config-controller.js'; | ||||
| import { ContextController } from '../../features/context/context.js'; | ||||
| import ClientMetricsController from '../../features/metrics/client-metrics/client-metrics.js'; | ||||
| import TagController from './tag.js'; | ||||
| @ -90,7 +90,7 @@ export class AdminApi extends Controller { | ||||
| 
 | ||||
|         this.app.use( | ||||
|             '/ui-config', | ||||
|             new ConfigController(config, services).router, | ||||
|             new UiConfigController(config, services).router, | ||||
|         ); | ||||
|         this.app.use( | ||||
|             '/context', | ||||
|  | ||||
| @ -170,6 +170,7 @@ import type { IPrivateProjectChecker } from '../features/private-project/private | ||||
| import { UnknownFlagsService } from '../features/metrics/unknown-flags/unknown-flags-service.js'; | ||||
| import type FeatureLinkService from '../features/feature-links/feature-link-service.js'; | ||||
| import { createUserService } from '../features/users/createUserService.js'; | ||||
| import { UiConfigService } from '../ui-config/ui-config-service.js'; | ||||
| 
 | ||||
| export const createServices = ( | ||||
|     stores: IUnleashStores, | ||||
| @ -441,6 +442,15 @@ export const createServices = ( | ||||
|         ? withTransactional(createUserSubscriptionsService(config), db) | ||||
|         : withFakeTransactional(createFakeUserSubscriptionsService(config)); | ||||
| 
 | ||||
|     const uiConfigService = new UiConfigService(config, { | ||||
|         versionService, | ||||
|         settingService, | ||||
|         emailService, | ||||
|         frontendApiService, | ||||
|         maintenanceService, | ||||
|         sessionService, | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|         transactionalAccessService, | ||||
|         accessService, | ||||
| @ -514,6 +524,7 @@ export const createServices = ( | ||||
|         transactionalFeatureLinkService, | ||||
|         featureLinkService, | ||||
|         unknownFlagsService, | ||||
|         uiConfigService, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| @ -645,4 +656,5 @@ export interface IUnleashServices { | ||||
|     transactionalFeatureLinkService: WithTransactional<FeatureLinkService>; | ||||
|     featureLinkService: FeatureLinkService; | ||||
|     unknownFlagsService: UnknownFlagsService; | ||||
|     uiConfigService: UiConfigService; | ||||
| } | ||||
|  | ||||
| @ -59,7 +59,8 @@ export type IFlagKey = | ||||
|     | 'fetchMode' | ||||
|     | 'optimizeLifecycle' | ||||
|     | 'newStrategyModal' | ||||
|     | 'globalChangeRequestList'; | ||||
|     | 'globalChangeRequestList' | ||||
|     | 'newUiConfigService'; | ||||
| 
 | ||||
| export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; | ||||
| 
 | ||||
| @ -273,6 +274,10 @@ const flags: IFlags = { | ||||
|         process.env.UNLEASH_EXPERIMENTAL_GLOBAL_CHANGE_REQUEST_LIST, | ||||
|         false, | ||||
|     ), | ||||
|     newUiConfigService: parseEnvVarBoolean( | ||||
|         process.env.UNLEASH_EXPERIMENTAL_NEW_UI_CONFIG_SERVICE, | ||||
|         false, | ||||
|     ), | ||||
| }; | ||||
| 
 | ||||
| export const defaultExperimentalOptions: IExperimentalOptions = { | ||||
|  | ||||
| @ -1,37 +1,34 @@ | ||||
| import type { Response } from 'express'; | ||||
| import type { AuthedRequest } from '../../types/core.js'; | ||||
| import type { IUnleashServices } from '../../services/index.js'; | ||||
| import { IAuthType, type IUnleashConfig } from '../../types/option.js'; | ||||
| import version from '../../util/version.js'; | ||||
| import Controller from '../controller.js'; | ||||
| import type VersionService from '../../services/version-service.js'; | ||||
| import type SettingService from '../../services/setting-service.js'; | ||||
| import type { AuthedRequest } from '../types/core.js'; | ||||
| import type { IUnleashServices } from '../services/index.js'; | ||||
| import { IAuthType, type IUnleashConfig } from '../types/option.js'; | ||||
| import version from '../util/version.js'; | ||||
| import Controller from '../routes/controller.js'; | ||||
| import type VersionService from '../services/version-service.js'; | ||||
| import type SettingService from '../services/setting-service.js'; | ||||
| import { | ||||
|     type SimpleAuthSettings, | ||||
|     simpleAuthSettingsKey, | ||||
| } from '../../types/settings/simple-auth-settings.js'; | ||||
| import { ADMIN, NONE, UPDATE_CORS } from '../../types/permissions.js'; | ||||
| import { createResponseSchema } from '../../openapi/util/create-response-schema.js'; | ||||
| } from '../types/settings/simple-auth-settings.js'; | ||||
| import { ADMIN, NONE, UPDATE_CORS } from '../types/permissions.js'; | ||||
| import { createResponseSchema } from '../openapi/util/create-response-schema.js'; | ||||
| import { | ||||
|     uiConfigSchema, | ||||
|     type UiConfigSchema, | ||||
| } from '../../openapi/spec/ui-config-schema.js'; | ||||
| import type { OpenApiService } from '../../services/openapi-service.js'; | ||||
| import type { EmailService } from '../../services/email-service.js'; | ||||
| import { emptyResponse } from '../../openapi/util/standard-responses.js'; | ||||
| import type { IAuthRequest } from '../unleash-types.js'; | ||||
| import NotFoundError from '../../error/notfound-error.js'; | ||||
| import type { SetCorsSchema } from '../../openapi/spec/set-cors-schema.js'; | ||||
| import { createRequestSchema } from '../../openapi/util/create-request-schema.js'; | ||||
| import type { | ||||
|     FrontendApiService, | ||||
|     SessionService, | ||||
| } from '../../services/index.js'; | ||||
| import type MaintenanceService from '../../features/maintenance/maintenance-service.js'; | ||||
| import type ClientInstanceService from '../../features/metrics/instance/instance-service.js'; | ||||
| import type { IFlagResolver } from '../../types/index.js'; | ||||
| } from '../openapi/spec/ui-config-schema.js'; | ||||
| import type { OpenApiService } from '../services/openapi-service.js'; | ||||
| import type { EmailService } from '../services/email-service.js'; | ||||
| import { emptyResponse } from '../openapi/util/standard-responses.js'; | ||||
| import type { IAuthRequest } from '../routes/unleash-types.js'; | ||||
| import NotFoundError from '../error/notfound-error.js'; | ||||
| import type { SetCorsSchema } from '../openapi/spec/set-cors-schema.js'; | ||||
| import { createRequestSchema } from '../openapi/util/create-request-schema.js'; | ||||
| import type { FrontendApiService, SessionService } from '../services/index.js'; | ||||
| import type MaintenanceService from '../features/maintenance/maintenance-service.js'; | ||||
| import type { IFlagResolver } from '../types/index.js'; | ||||
| import type { UiConfigService } from './ui-config-service.js'; | ||||
| 
 | ||||
| class ConfigController extends Controller { | ||||
| class UiConfigController extends Controller { | ||||
|     private versionService: VersionService; | ||||
| 
 | ||||
|     private settingService: SettingService; | ||||
| @ -40,14 +37,14 @@ class ConfigController extends Controller { | ||||
| 
 | ||||
|     private emailService: EmailService; | ||||
| 
 | ||||
|     private clientInstanceService: ClientInstanceService; | ||||
| 
 | ||||
|     private sessionService: SessionService; | ||||
| 
 | ||||
|     private maintenanceService: MaintenanceService; | ||||
| 
 | ||||
|     private flagResolver: IFlagResolver; | ||||
| 
 | ||||
|     private uiConfigService: UiConfigService; | ||||
| 
 | ||||
|     private readonly openApiService: OpenApiService; | ||||
| 
 | ||||
|     constructor( | ||||
| @ -59,8 +56,8 @@ class ConfigController extends Controller { | ||||
|             openApiService, | ||||
|             frontendApiService, | ||||
|             maintenanceService, | ||||
|             clientInstanceService, | ||||
|             sessionService, | ||||
|             uiConfigService, | ||||
|         }: Pick< | ||||
|             IUnleashServices, | ||||
|             | 'versionService' | ||||
| @ -71,18 +68,20 @@ class ConfigController extends Controller { | ||||
|             | 'maintenanceService' | ||||
|             | 'clientInstanceService' | ||||
|             | 'sessionService' | ||||
|             | 'uiConfigService' | ||||
|         >, | ||||
|     ) { | ||||
|         super(config); | ||||
|         this.flagResolver = config.flagResolver; | ||||
|         this.openApiService = openApiService; | ||||
|         this.uiConfigService = uiConfigService; | ||||
|         this.versionService = versionService; | ||||
|         this.settingService = settingService; | ||||
|         this.emailService = emailService; | ||||
|         this.openApiService = openApiService; | ||||
|         this.frontendApiService = frontendApiService; | ||||
|         this.maintenanceService = maintenanceService; | ||||
|         this.clientInstanceService = clientInstanceService; | ||||
|         this.sessionService = sessionService; | ||||
|         this.flagResolver = config.flagResolver; | ||||
| 
 | ||||
|         this.route({ | ||||
|             method: 'get', | ||||
|             path: '', | ||||
| @ -125,6 +124,17 @@ class ConfigController extends Controller { | ||||
|         req: AuthedRequest, | ||||
|         res: Response<UiConfigSchema>, | ||||
|     ): Promise<void> { | ||||
|         if (this.flagResolver.isEnabled('newUiConfigService')) { | ||||
|             const uiConfig = await this.uiConfigService.getUiConfig(req.user); | ||||
| 
 | ||||
|             return this.openApiService.respondWithValidation( | ||||
|                 200, | ||||
|                 res, | ||||
|                 uiConfigSchema.$id, | ||||
|                 uiConfig, | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         const getMaxSessionsCount = async () => { | ||||
|             if (this.flagResolver.isEnabled('showUserDeviceCount')) { | ||||
|                 return this.sessionService.getMaxSessionsCount(); | ||||
| @ -207,4 +217,4 @@ class ConfigController extends Controller { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default ConfigController; | ||||
| export default UiConfigController; | ||||
							
								
								
									
										127
									
								
								src/lib/ui-config/ui-config-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/lib/ui-config/ui-config-service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| import type { IUnleashConfig } from '../types/option.js'; | ||||
| import type { UiConfigSchema } from '../openapi/index.js'; | ||||
| import { | ||||
|     IAuthType, | ||||
|     type EmailService, | ||||
|     type FrontendApiService, | ||||
|     type IFlagResolver, | ||||
|     type IUnleashServices, | ||||
|     type SessionService, | ||||
|     type SettingService, | ||||
|     type User, | ||||
|     type VersionService, | ||||
| } from '../server-impl.js'; | ||||
| import type MaintenanceService from '../features/maintenance/maintenance-service.js'; | ||||
| import { | ||||
|     type SimpleAuthSettings, | ||||
|     simpleAuthSettingsKey, | ||||
| } from '../types/settings/simple-auth-settings.js'; | ||||
| import version from '../util/version.js'; | ||||
| 
 | ||||
| export class UiConfigService { | ||||
|     private config: IUnleashConfig; | ||||
| 
 | ||||
|     private versionService: VersionService; | ||||
| 
 | ||||
|     private settingService: SettingService; | ||||
| 
 | ||||
|     private frontendApiService: FrontendApiService; | ||||
| 
 | ||||
|     private emailService: EmailService; | ||||
| 
 | ||||
|     private sessionService: SessionService; | ||||
| 
 | ||||
|     private maintenanceService: MaintenanceService; | ||||
| 
 | ||||
|     private flagResolver: IFlagResolver; | ||||
| 
 | ||||
|     constructor( | ||||
|         config: IUnleashConfig, | ||||
|         { | ||||
|             versionService, | ||||
|             settingService, | ||||
|             emailService, | ||||
|             frontendApiService, | ||||
|             maintenanceService, | ||||
|             sessionService, | ||||
|         }: Pick< | ||||
|             IUnleashServices, | ||||
|             | 'versionService' | ||||
|             | 'settingService' | ||||
|             | 'emailService' | ||||
|             | 'frontendApiService' | ||||
|             | 'maintenanceService' | ||||
|             | 'sessionService' | ||||
|         >, | ||||
|     ) { | ||||
|         this.config = config; | ||||
|         this.flagResolver = config.flagResolver; | ||||
|         this.versionService = versionService; | ||||
|         this.settingService = settingService; | ||||
|         this.emailService = emailService; | ||||
|         this.frontendApiService = frontendApiService; | ||||
|         this.maintenanceService = maintenanceService; | ||||
|         this.sessionService = sessionService; | ||||
|     } | ||||
| 
 | ||||
|     async getMaxSessionsCount(): Promise<number> { | ||||
|         if (this.flagResolver.isEnabled('showUserDeviceCount')) { | ||||
|             return this.sessionService.getMaxSessionsCount(); | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     async getUiConfig(user: User): Promise<UiConfigSchema> { | ||||
|         const [ | ||||
|             frontendSettings, | ||||
|             simpleAuthSettings, | ||||
|             maintenanceMode, | ||||
|             maxSessionsCount, | ||||
|         ] = await Promise.all([ | ||||
|             this.frontendApiService.getFrontendSettings(false), | ||||
|             this.settingService.get<SimpleAuthSettings>(simpleAuthSettingsKey), | ||||
|             this.maintenanceService.isMaintenanceMode(), | ||||
|             this.getMaxSessionsCount(), | ||||
|         ]); | ||||
| 
 | ||||
|         const disablePasswordAuth = | ||||
|             simpleAuthSettings?.disabled || | ||||
|             this.config.authentication.type === IAuthType.NONE; | ||||
| 
 | ||||
|         const expFlags = this.config.flagResolver.getAll({ | ||||
|             email: user.email, | ||||
|         }); | ||||
| 
 | ||||
|         const flags = { | ||||
|             ...this.config.ui.flags, | ||||
|             ...expFlags, | ||||
|         }; | ||||
| 
 | ||||
|         const unleashContext = { | ||||
|             ...this.flagResolver.getStaticContext(), | ||||
|             email: user.email, | ||||
|             userId: user.id, | ||||
|         }; | ||||
| 
 | ||||
|         const uiConfig: UiConfigSchema = { | ||||
|             ...this.config.ui, | ||||
|             flags, | ||||
|             version, | ||||
|             emailEnabled: this.emailService.isEnabled(), | ||||
|             unleashUrl: this.config.server.unleashUrl, | ||||
|             baseUriPath: this.config.server.baseUriPath, | ||||
|             authenticationType: this.config.authentication?.type, | ||||
|             frontendApiOrigins: frontendSettings.frontendApiOrigins, | ||||
|             versionInfo: await this.versionService.getVersionInfo(), | ||||
|             prometheusAPIAvailable: this.config.prometheusApi !== undefined, | ||||
|             resourceLimits: this.config.resourceLimits, | ||||
|             disablePasswordAuth, | ||||
|             maintenanceMode, | ||||
|             feedbackUriPath: this.config.feedbackUriPath, | ||||
|             maxSessionsCount, | ||||
|             unleashContext: unleashContext, | ||||
|         }; | ||||
| 
 | ||||
|         return uiConfig; | ||||
|     } | ||||
| } | ||||
| @ -1,15 +1,15 @@ | ||||
| import supertest, { type Test } from 'supertest'; | ||||
| import { createTestConfig } from '../../../test/config/test-config.js'; | ||||
| import { createTestConfig } from '../../test/config/test-config.js'; | ||||
| 
 | ||||
| import createStores from '../../../test/fixtures/store.js'; | ||||
| import getApp from '../../app.js'; | ||||
| import { createServices } from '../../services/index.js'; | ||||
| import createStores from '../../test/fixtures/store.js'; | ||||
| import getApp from '../app.js'; | ||||
| import { createServices } from '../services/index.js'; | ||||
| import { | ||||
|     DEFAULT_SEGMENT_VALUES_LIMIT, | ||||
|     DEFAULT_STRATEGY_SEGMENTS_LIMIT, | ||||
| } from '../../util/segments.js'; | ||||
| } from '../util/segments.js'; | ||||
| import type TestAgent from 'supertest/lib/agent.d.ts'; | ||||
| import type { IUnleashStores } from '../../types/index.js'; | ||||
| import type { IUnleashStores } from '../types/index.js'; | ||||
| 
 | ||||
| const uiConfig = { | ||||
|     headerBackground: 'red', | ||||
| @ -55,6 +55,7 @@ process.nextTick(async () => { | ||||
|                         lifecycleGraphs: true, | ||||
|                         newStrategyModal: true, | ||||
|                         globalChangeRequestList: true, | ||||
|                         newUiConfigService: true, | ||||
|                     }, | ||||
|                 }, | ||||
|                 authentication: { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user