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 PlaygroundController from '../../features/playground/playground.js'; | ||||||
| import MetricsController from './metrics.js'; | import MetricsController from './metrics.js'; | ||||||
| import UserController from './user/user.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 { ContextController } from '../../features/context/context.js'; | ||||||
| import ClientMetricsController from '../../features/metrics/client-metrics/client-metrics.js'; | import ClientMetricsController from '../../features/metrics/client-metrics/client-metrics.js'; | ||||||
| import TagController from './tag.js'; | import TagController from './tag.js'; | ||||||
| @ -90,7 +90,7 @@ export class AdminApi extends Controller { | |||||||
| 
 | 
 | ||||||
|         this.app.use( |         this.app.use( | ||||||
|             '/ui-config', |             '/ui-config', | ||||||
|             new ConfigController(config, services).router, |             new UiConfigController(config, services).router, | ||||||
|         ); |         ); | ||||||
|         this.app.use( |         this.app.use( | ||||||
|             '/context', |             '/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 { UnknownFlagsService } from '../features/metrics/unknown-flags/unknown-flags-service.js'; | ||||||
| import type FeatureLinkService from '../features/feature-links/feature-link-service.js'; | import type FeatureLinkService from '../features/feature-links/feature-link-service.js'; | ||||||
| import { createUserService } from '../features/users/createUserService.js'; | import { createUserService } from '../features/users/createUserService.js'; | ||||||
|  | import { UiConfigService } from '../ui-config/ui-config-service.js'; | ||||||
| 
 | 
 | ||||||
| export const createServices = ( | export const createServices = ( | ||||||
|     stores: IUnleashStores, |     stores: IUnleashStores, | ||||||
| @ -441,6 +442,15 @@ export const createServices = ( | |||||||
|         ? withTransactional(createUserSubscriptionsService(config), db) |         ? withTransactional(createUserSubscriptionsService(config), db) | ||||||
|         : withFakeTransactional(createFakeUserSubscriptionsService(config)); |         : withFakeTransactional(createFakeUserSubscriptionsService(config)); | ||||||
| 
 | 
 | ||||||
|  |     const uiConfigService = new UiConfigService(config, { | ||||||
|  |         versionService, | ||||||
|  |         settingService, | ||||||
|  |         emailService, | ||||||
|  |         frontendApiService, | ||||||
|  |         maintenanceService, | ||||||
|  |         sessionService, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|         transactionalAccessService, |         transactionalAccessService, | ||||||
|         accessService, |         accessService, | ||||||
| @ -514,6 +524,7 @@ export const createServices = ( | |||||||
|         transactionalFeatureLinkService, |         transactionalFeatureLinkService, | ||||||
|         featureLinkService, |         featureLinkService, | ||||||
|         unknownFlagsService, |         unknownFlagsService, | ||||||
|  |         uiConfigService, | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -645,4 +656,5 @@ export interface IUnleashServices { | |||||||
|     transactionalFeatureLinkService: WithTransactional<FeatureLinkService>; |     transactionalFeatureLinkService: WithTransactional<FeatureLinkService>; | ||||||
|     featureLinkService: FeatureLinkService; |     featureLinkService: FeatureLinkService; | ||||||
|     unknownFlagsService: UnknownFlagsService; |     unknownFlagsService: UnknownFlagsService; | ||||||
|  |     uiConfigService: UiConfigService; | ||||||
| } | } | ||||||
|  | |||||||
| @ -59,7 +59,8 @@ export type IFlagKey = | |||||||
|     | 'fetchMode' |     | 'fetchMode' | ||||||
|     | 'optimizeLifecycle' |     | 'optimizeLifecycle' | ||||||
|     | 'newStrategyModal' |     | 'newStrategyModal' | ||||||
|     | 'globalChangeRequestList'; |     | 'globalChangeRequestList' | ||||||
|  |     | 'newUiConfigService'; | ||||||
| 
 | 
 | ||||||
| export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; | export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; | ||||||
| 
 | 
 | ||||||
| @ -273,6 +274,10 @@ const flags: IFlags = { | |||||||
|         process.env.UNLEASH_EXPERIMENTAL_GLOBAL_CHANGE_REQUEST_LIST, |         process.env.UNLEASH_EXPERIMENTAL_GLOBAL_CHANGE_REQUEST_LIST, | ||||||
|         false, |         false, | ||||||
|     ), |     ), | ||||||
|  |     newUiConfigService: parseEnvVarBoolean( | ||||||
|  |         process.env.UNLEASH_EXPERIMENTAL_NEW_UI_CONFIG_SERVICE, | ||||||
|  |         false, | ||||||
|  |     ), | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const defaultExperimentalOptions: IExperimentalOptions = { | export const defaultExperimentalOptions: IExperimentalOptions = { | ||||||
|  | |||||||
| @ -1,37 +1,34 @@ | |||||||
| import type { Response } from 'express'; | import type { Response } from 'express'; | ||||||
| import type { AuthedRequest } from '../../types/core.js'; | import type { AuthedRequest } from '../types/core.js'; | ||||||
| import type { IUnleashServices } from '../../services/index.js'; | import type { IUnleashServices } from '../services/index.js'; | ||||||
| import { IAuthType, type IUnleashConfig } from '../../types/option.js'; | import { IAuthType, type IUnleashConfig } from '../types/option.js'; | ||||||
| import version from '../../util/version.js'; | import version from '../util/version.js'; | ||||||
| import Controller from '../controller.js'; | import Controller from '../routes/controller.js'; | ||||||
| import type VersionService from '../../services/version-service.js'; | import type VersionService from '../services/version-service.js'; | ||||||
| import type SettingService from '../../services/setting-service.js'; | import type SettingService from '../services/setting-service.js'; | ||||||
| import { | import { | ||||||
|     type SimpleAuthSettings, |     type SimpleAuthSettings, | ||||||
|     simpleAuthSettingsKey, |     simpleAuthSettingsKey, | ||||||
| } from '../../types/settings/simple-auth-settings.js'; | } from '../types/settings/simple-auth-settings.js'; | ||||||
| import { ADMIN, NONE, UPDATE_CORS } from '../../types/permissions.js'; | import { ADMIN, NONE, UPDATE_CORS } from '../types/permissions.js'; | ||||||
| import { createResponseSchema } from '../../openapi/util/create-response-schema.js'; | import { createResponseSchema } from '../openapi/util/create-response-schema.js'; | ||||||
| import { | import { | ||||||
|     uiConfigSchema, |     uiConfigSchema, | ||||||
|     type UiConfigSchema, |     type UiConfigSchema, | ||||||
| } from '../../openapi/spec/ui-config-schema.js'; | } from '../openapi/spec/ui-config-schema.js'; | ||||||
| import type { OpenApiService } from '../../services/openapi-service.js'; | import type { OpenApiService } from '../services/openapi-service.js'; | ||||||
| import type { EmailService } from '../../services/email-service.js'; | import type { EmailService } from '../services/email-service.js'; | ||||||
| import { emptyResponse } from '../../openapi/util/standard-responses.js'; | import { emptyResponse } from '../openapi/util/standard-responses.js'; | ||||||
| import type { IAuthRequest } from '../unleash-types.js'; | import type { IAuthRequest } from '../routes/unleash-types.js'; | ||||||
| import NotFoundError from '../../error/notfound-error.js'; | import NotFoundError from '../error/notfound-error.js'; | ||||||
| import type { SetCorsSchema } from '../../openapi/spec/set-cors-schema.js'; | import type { SetCorsSchema } from '../openapi/spec/set-cors-schema.js'; | ||||||
| import { createRequestSchema } from '../../openapi/util/create-request-schema.js'; | import { createRequestSchema } from '../openapi/util/create-request-schema.js'; | ||||||
| import type { | import type { FrontendApiService, SessionService } from '../services/index.js'; | ||||||
|     FrontendApiService, | import type MaintenanceService from '../features/maintenance/maintenance-service.js'; | ||||||
|     SessionService, | import type { IFlagResolver } from '../types/index.js'; | ||||||
| } from '../../services/index.js'; | import type { UiConfigService } from './ui-config-service.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'; |  | ||||||
| 
 | 
 | ||||||
| class ConfigController extends Controller { | class UiConfigController extends Controller { | ||||||
|     private versionService: VersionService; |     private versionService: VersionService; | ||||||
| 
 | 
 | ||||||
|     private settingService: SettingService; |     private settingService: SettingService; | ||||||
| @ -40,14 +37,14 @@ class ConfigController extends Controller { | |||||||
| 
 | 
 | ||||||
|     private emailService: EmailService; |     private emailService: EmailService; | ||||||
| 
 | 
 | ||||||
|     private clientInstanceService: ClientInstanceService; |  | ||||||
| 
 |  | ||||||
|     private sessionService: SessionService; |     private sessionService: SessionService; | ||||||
| 
 | 
 | ||||||
|     private maintenanceService: MaintenanceService; |     private maintenanceService: MaintenanceService; | ||||||
| 
 | 
 | ||||||
|     private flagResolver: IFlagResolver; |     private flagResolver: IFlagResolver; | ||||||
| 
 | 
 | ||||||
|  |     private uiConfigService: UiConfigService; | ||||||
|  | 
 | ||||||
|     private readonly openApiService: OpenApiService; |     private readonly openApiService: OpenApiService; | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
| @ -59,8 +56,8 @@ class ConfigController extends Controller { | |||||||
|             openApiService, |             openApiService, | ||||||
|             frontendApiService, |             frontendApiService, | ||||||
|             maintenanceService, |             maintenanceService, | ||||||
|             clientInstanceService, |  | ||||||
|             sessionService, |             sessionService, | ||||||
|  |             uiConfigService, | ||||||
|         }: Pick< |         }: Pick< | ||||||
|             IUnleashServices, |             IUnleashServices, | ||||||
|             | 'versionService' |             | 'versionService' | ||||||
| @ -71,18 +68,20 @@ class ConfigController extends Controller { | |||||||
|             | 'maintenanceService' |             | 'maintenanceService' | ||||||
|             | 'clientInstanceService' |             | 'clientInstanceService' | ||||||
|             | 'sessionService' |             | 'sessionService' | ||||||
|  |             | 'uiConfigService' | ||||||
|         >, |         >, | ||||||
|     ) { |     ) { | ||||||
|         super(config); |         super(config); | ||||||
|  |         this.flagResolver = config.flagResolver; | ||||||
|  |         this.openApiService = openApiService; | ||||||
|  |         this.uiConfigService = uiConfigService; | ||||||
|         this.versionService = versionService; |         this.versionService = versionService; | ||||||
|         this.settingService = settingService; |         this.settingService = settingService; | ||||||
|         this.emailService = emailService; |         this.emailService = emailService; | ||||||
|         this.openApiService = openApiService; |  | ||||||
|         this.frontendApiService = frontendApiService; |         this.frontendApiService = frontendApiService; | ||||||
|         this.maintenanceService = maintenanceService; |         this.maintenanceService = maintenanceService; | ||||||
|         this.clientInstanceService = clientInstanceService; |  | ||||||
|         this.sessionService = sessionService; |         this.sessionService = sessionService; | ||||||
|         this.flagResolver = config.flagResolver; | 
 | ||||||
|         this.route({ |         this.route({ | ||||||
|             method: 'get', |             method: 'get', | ||||||
|             path: '', |             path: '', | ||||||
| @ -125,6 +124,17 @@ class ConfigController extends Controller { | |||||||
|         req: AuthedRequest, |         req: AuthedRequest, | ||||||
|         res: Response<UiConfigSchema>, |         res: Response<UiConfigSchema>, | ||||||
|     ): Promise<void> { |     ): 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 () => { |         const getMaxSessionsCount = async () => { | ||||||
|             if (this.flagResolver.isEnabled('showUserDeviceCount')) { |             if (this.flagResolver.isEnabled('showUserDeviceCount')) { | ||||||
|                 return this.sessionService.getMaxSessionsCount(); |                 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 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 createStores from '../../test/fixtures/store.js'; | ||||||
| import getApp from '../../app.js'; | import getApp from '../app.js'; | ||||||
| import { createServices } from '../../services/index.js'; | import { createServices } from '../services/index.js'; | ||||||
| import { | import { | ||||||
|     DEFAULT_SEGMENT_VALUES_LIMIT, |     DEFAULT_SEGMENT_VALUES_LIMIT, | ||||||
|     DEFAULT_STRATEGY_SEGMENTS_LIMIT, |     DEFAULT_STRATEGY_SEGMENTS_LIMIT, | ||||||
| } from '../../util/segments.js'; | } from '../util/segments.js'; | ||||||
| import type TestAgent from 'supertest/lib/agent.d.ts'; | 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 = { | const uiConfig = { | ||||||
|     headerBackground: 'red', |     headerBackground: 'red', | ||||||
| @ -55,6 +55,7 @@ process.nextTick(async () => { | |||||||
|                         lifecycleGraphs: true, |                         lifecycleGraphs: true, | ||||||
|                         newStrategyModal: true, |                         newStrategyModal: true, | ||||||
|                         globalChangeRequestList: true, |                         globalChangeRequestList: true, | ||||||
|  |                         newUiConfigService: true, | ||||||
|                     }, |                     }, | ||||||
|                 }, |                 }, | ||||||
|                 authentication: { |                 authentication: { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user