diff --git a/.commit b/.commit index 104e465e8..0d1af1377 100644 --- a/.commit +++ b/.commit @@ -1 +1 @@ -d34e16125bbef14cf07b1e061eab4d0bb9cf8f89 +ebc8751bde170ebbe37e86849f6bcfb48150dec7 diff --git a/.sync-history b/.sync-history index 1b2287d21..6f9099bf2 100644 --- a/.sync-history +++ b/.sync-history @@ -1,2 +1 @@ -83aec620e 2026-06-17 Merged PR 93487: #713974 - Web Portal: Fix SearchMode and RoleAssignmentsFilter reset in IT Shop and Attestation approval APIs -23ef9ee39 2026-06-08 Merged PR 93299: PBI:#710272 - CPL: Fix column name casing for IsWorkingCopy filter \ No newline at end of file +ebc8751bd 2026-06-29 Merged PR 93370: #713400 - Parallelize Web Portal initial page load (App + Start) and make Proj... \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8531d65..784f8234d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### June 29, 2026 +- 713400: Fixing an issue with Web Portal init page load time. + ### June 18, 2026 - 710272: Fixing an issue with Rule violations filter column mismatch. - 722179: Fixing an issue with new request approval workflow valid dates. diff --git a/imxweb/imx-modules/imx-api-aad.tgz b/imxweb/imx-modules/imx-api-aad.tgz index 65d3d0ffd..0b142d5d2 100644 Binary files a/imxweb/imx-modules/imx-api-aad.tgz and b/imxweb/imx-modules/imx-api-aad.tgz differ diff --git a/imxweb/imx-modules/imx-api-aob.tgz b/imxweb/imx-modules/imx-api-aob.tgz index 9f164496f..fc443310b 100644 Binary files a/imxweb/imx-modules/imx-api-aob.tgz and b/imxweb/imx-modules/imx-api-aob.tgz differ diff --git a/imxweb/imx-modules/imx-api-apc.tgz b/imxweb/imx-modules/imx-api-apc.tgz index 6eec7c961..c4dec969f 100644 Binary files a/imxweb/imx-modules/imx-api-apc.tgz and b/imxweb/imx-modules/imx-api-apc.tgz differ diff --git a/imxweb/imx-modules/imx-api-att.tgz b/imxweb/imx-modules/imx-api-att.tgz index 59fbf029c..19b528981 100644 Binary files a/imxweb/imx-modules/imx-api-att.tgz and b/imxweb/imx-modules/imx-api-att.tgz differ diff --git a/imxweb/imx-modules/imx-api-cpl.tgz b/imxweb/imx-modules/imx-api-cpl.tgz index d49e359b5..15aaf246e 100644 Binary files a/imxweb/imx-modules/imx-api-cpl.tgz and b/imxweb/imx-modules/imx-api-cpl.tgz differ diff --git a/imxweb/imx-modules/imx-api-dpr.tgz b/imxweb/imx-modules/imx-api-dpr.tgz index de76366e5..09c39d7ce 100644 Binary files a/imxweb/imx-modules/imx-api-dpr.tgz and b/imxweb/imx-modules/imx-api-dpr.tgz differ diff --git a/imxweb/imx-modules/imx-api-hds.tgz b/imxweb/imx-modules/imx-api-hds.tgz index d1b885ed3..eccaa8fd9 100644 Binary files a/imxweb/imx-modules/imx-api-hds.tgz and b/imxweb/imx-modules/imx-api-hds.tgz differ diff --git a/imxweb/imx-modules/imx-api-o3e.tgz b/imxweb/imx-modules/imx-api-o3e.tgz index 660b3170e..14d8b86b5 100644 Binary files a/imxweb/imx-modules/imx-api-o3e.tgz and b/imxweb/imx-modules/imx-api-o3e.tgz differ diff --git a/imxweb/imx-modules/imx-api-o3t.tgz b/imxweb/imx-modules/imx-api-o3t.tgz index 52919c858..b8d9e63f7 100644 Binary files a/imxweb/imx-modules/imx-api-o3t.tgz and b/imxweb/imx-modules/imx-api-o3t.tgz differ diff --git a/imxweb/imx-modules/imx-api-olg.tgz b/imxweb/imx-modules/imx-api-olg.tgz index b3b994ea8..fe9cabded 100644 Binary files a/imxweb/imx-modules/imx-api-olg.tgz and b/imxweb/imx-modules/imx-api-olg.tgz differ diff --git a/imxweb/imx-modules/imx-api-pol.tgz b/imxweb/imx-modules/imx-api-pol.tgz index 90c7cf1b0..b9d7c745d 100644 Binary files a/imxweb/imx-modules/imx-api-pol.tgz and b/imxweb/imx-modules/imx-api-pol.tgz differ diff --git a/imxweb/imx-modules/imx-api-qbm.tgz b/imxweb/imx-modules/imx-api-qbm.tgz index ac7358c22..e0d35fce3 100644 Binary files a/imxweb/imx-modules/imx-api-qbm.tgz and b/imxweb/imx-modules/imx-api-qbm.tgz differ diff --git a/imxweb/imx-modules/imx-api-qer.tgz b/imxweb/imx-modules/imx-api-qer.tgz index ae915c75e..320ed9628 100644 Binary files a/imxweb/imx-modules/imx-api-qer.tgz and b/imxweb/imx-modules/imx-api-qer.tgz differ diff --git a/imxweb/imx-modules/imx-api-rmb.tgz b/imxweb/imx-modules/imx-api-rmb.tgz index e12c1672b..52046b582 100644 Binary files a/imxweb/imx-modules/imx-api-rmb.tgz and b/imxweb/imx-modules/imx-api-rmb.tgz differ diff --git a/imxweb/imx-modules/imx-api-rms.tgz b/imxweb/imx-modules/imx-api-rms.tgz index 789949311..f8ce92624 100644 Binary files a/imxweb/imx-modules/imx-api-rms.tgz and b/imxweb/imx-modules/imx-api-rms.tgz differ diff --git a/imxweb/imx-modules/imx-api-rps.tgz b/imxweb/imx-modules/imx-api-rps.tgz index cf7eca597..5cbfbf3ed 100644 Binary files a/imxweb/imx-modules/imx-api-rps.tgz and b/imxweb/imx-modules/imx-api-rps.tgz differ diff --git a/imxweb/imx-modules/imx-api-sac.tgz b/imxweb/imx-modules/imx-api-sac.tgz index 2474c9bad..60cd346dd 100644 Binary files a/imxweb/imx-modules/imx-api-sac.tgz and b/imxweb/imx-modules/imx-api-sac.tgz differ diff --git a/imxweb/imx-modules/imx-api-tsb.tgz b/imxweb/imx-modules/imx-api-tsb.tgz index 0e97fae01..b3cbf2eab 100644 Binary files a/imxweb/imx-modules/imx-api-tsb.tgz and b/imxweb/imx-modules/imx-api-tsb.tgz differ diff --git a/imxweb/imx-modules/imx-api-uci.tgz b/imxweb/imx-modules/imx-api-uci.tgz index 8d8305ac2..ce2c32bf4 100644 Binary files a/imxweb/imx-modules/imx-api-uci.tgz and b/imxweb/imx-modules/imx-api-uci.tgz differ diff --git a/imxweb/imx-modules/imx-api.tgz b/imxweb/imx-modules/imx-api.tgz index 350c762aa..589880997 100644 Binary files a/imxweb/imx-modules/imx-api.tgz and b/imxweb/imx-modules/imx-api.tgz differ diff --git a/imxweb/imx-modules/imx-qbm-dbts.tgz b/imxweb/imx-modules/imx-qbm-dbts.tgz index 0df798073..638b4a3dd 100644 Binary files a/imxweb/imx-modules/imx-qbm-dbts.tgz and b/imxweb/imx-modules/imx-qbm-dbts.tgz differ diff --git a/imxweb/projects/qer-app-portal/src/app/app.component.ts b/imxweb/projects/qer-app-portal/src/app/app.component.ts index 61972b656..8d0347a29 100644 --- a/imxweb/projects/qer-app-portal/src/app/app.component.ts +++ b/imxweb/projects/qer-app-portal/src/app/app.component.ts @@ -96,11 +96,20 @@ export class AppComponent implements OnInit, OnDestroy { // Needs to close here when running in containers (auth skipped) splash.close(); - const config: QerProjectConfig & ProjectConfig = await projectConfig.getConfig(); - const features = (await userModelService.getFeatures()).Features; - const systemInfo = await systemInfoService.get(); - const groups = (await userModelService.getGroups()).map((group) => group.Name || ''); - this.profileSettings = await this.qerClient.v2Client.portal_profile_get(); + // The following calls are independent of each other and can be issued in parallel. + // This is important on installations where individual REST calls may be slow + // (e.g. read-only secondary SQL databases): the post-login wall-clock time + // is bounded by the slowest single call instead of the sum. + const [config, featuresResp, systemInfo, groupsResp, profileSettings] = await Promise.all([ + projectConfig.getConfig() as Promise, + userModelService.getFeatures(), + systemInfoService.get(), + userModelService.getGroups(), + this.qerClient.v2Client.portal_profile_get(), + ]); + const features = featuresResp.Features; + const groups = groupsResp.map((group) => group.Name || ''); + this.profileSettings = profileSettings; const isUseProfileLangChecked = this.profileSettings.UseProfileLanguage ?? config.PersonConfig?.UseProfileCulture ?? false; // Set session culture if isUseProfileLangChecked is true, set browser culture otherwise if (isUseProfileLangChecked) { diff --git a/imxweb/projects/qer/src/lib/project-configuration/project-configuration.service.ts b/imxweb/projects/qer/src/lib/project-configuration/project-configuration.service.ts index 9647f2a85..793c74568 100644 --- a/imxweb/projects/qer/src/lib/project-configuration/project-configuration.service.ts +++ b/imxweb/projects/qer/src/lib/project-configuration/project-configuration.service.ts @@ -26,8 +26,9 @@ import { Injectable } from '@angular/core'; -import { ClassloggerService, SettingsService } from 'qbm'; import { ProjectConfig, QerProjectConfig } from 'imx-api-qer'; +import { CachedPromise } from 'imx-qbm-dbts'; +import { CacheService, ClassloggerService, SettingsService } from 'qbm'; import { QerApiService } from '../qer-api-client.service'; /** @@ -38,27 +39,31 @@ import { QerApiService } from '../qer-api-client.service'; providedIn: 'root' }) export class ProjectConfigurationService { - private projectConfig: QerProjectConfig & ProjectConfig; + private readonly projectConfigCache: CachedPromise; constructor(private qerClient: QerApiService, private readonly settings: SettingsService, - private readonly logger: ClassloggerService) { } + private readonly logger: ClassloggerService, + cacheService: CacheService) { + this.projectConfigCache = cacheService.buildCache(() => this.fetchConfig()); + } public async getConfig(): Promise { - if (this.projectConfig == null) { - this.logger.info(this, 'Project configuration is undefined. Retrieving...'); - this.projectConfig = { - ...await this.qerClient.client.portal_qer_projectconfig_get(), - ...await this.qerClient.client.portal_config_get() - }; - - this.logger.info(this, 'Received project configuration.'); - this.logger.trace(this, '', this.projectConfig); - } - - this.settings.DefaultPageSize = this.projectConfig.DefaultPageSize; + const projectConfig = await this.projectConfigCache.get(); + this.settings.DefaultPageSize = projectConfig.DefaultPageSize; + return projectConfig; + } - return this.projectConfig; + private async fetchConfig(): Promise { + this.logger.info(this, 'Fetching project configuration...'); + const [qerProjectConfig, projectConfig] = await Promise.all([ + this.qerClient.client.portal_qer_projectconfig_get(), + this.qerClient.client.portal_config_get() + ]); + const merged = { ...qerProjectConfig, ...projectConfig }; + this.logger.info(this, 'Received project configuration.'); + this.logger.trace(this, '', merged); + return merged; } } diff --git a/imxweb/projects/qer/src/lib/wport/start/start.component.ts b/imxweb/projects/qer/src/lib/wport/start/start.component.ts index 23e98ca8b..ca85c4a2a 100644 --- a/imxweb/projects/qer/src/lib/wport/start/start.component.ts +++ b/imxweb/projects/qer/src/lib/wport/start/start.component.ts @@ -57,7 +57,7 @@ export class StartComponent implements OnInit { private readonly detectRef: ChangeDetectorRef, private readonly projectConfigurationService: ProjectConfigurationService, private readonly splash: SplashService, - ) {} + ) { } public async ngOnInit(): Promise { this.dashboardService.busyStateChanged.subscribe((busy) => { @@ -66,11 +66,22 @@ export class StartComponent implements OnInit { }); const busy = this.dashboardService.beginBusy(); try { - this.userConfig = await this.userModelSvc.getUserConfig(); - this.pendingItems = await this.userModelSvc.getPendingItems(); - this.projectConfig = await this.projectConfigurationService.getConfig(); - this.systemInfo = await this.systemInfoService.get(); - this.userUid = (await this.sessionService.getSessionState()).UserUid; + // The following calls are independent of each other and can be issued in parallel. + // This is important on installations where individual REST calls may be slow + // (e.g. read-only secondary SQL databases): with Promise.all, the splash-screen + // wall-clock time is bounded by the slowest single call instead of the sum. + const [userConfig, pendingItems, projectConfig, systemInfo, sessionState] = await Promise.all([ + this.userModelSvc.getUserConfig(), + this.userModelSvc.getPendingItems(), + this.projectConfigurationService.getConfig(), + this.systemInfoService.get(), + this.sessionService.getSessionState(), + ]); + this.userConfig = userConfig; + this.pendingItems = pendingItems; + this.projectConfig = projectConfig; + this.systemInfo = systemInfo; + this.userUid = sessionState.UserUid; } finally { this.splash.close(); busy.endBusy();