diff --git a/ui/ui-frontend/projects/pastis/src/app/core/services/file.service.ts b/ui/ui-frontend/projects/pastis/src/app/core/services/file.service.ts index 113deba38bc..2dacc519dc8 100644 --- a/ui/ui-frontend/projects/pastis/src/app/core/services/file.service.ts +++ b/ui/ui-frontend/projects/pastis/src/app/core/services/file.service.ts @@ -133,6 +133,11 @@ export class FileService implements OnDestroy { * Get profile from backend with id */ getProfileAndUpdateTree(element: ProfileDescription) { + // Cancel previous request if still pending to avoid data race conditions + if (this._profileServiceGetProfileSubscription != null) { + this._profileServiceGetProfileSubscription.unsubscribe(); + } + this.loaderService.start(); this._profileServiceGetProfileSubscription = this.profileService .getProfile(element) diff --git a/ui/ui-frontend/projects/pastis/src/app/main/main.component.ts b/ui/ui-frontend/projects/pastis/src/app/main/main.component.ts index 6fca73e3679..826d5532637 100644 --- a/ui/ui-frontend/projects/pastis/src/app/main/main.component.ts +++ b/ui/ui-frontend/projects/pastis/src/app/main/main.component.ts @@ -74,7 +74,7 @@ knowledge of the CeCILL-C license and that you accept its terms. import { CdkTextareaAutosize } from '@angular/cdk/text-field'; import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { finalize, Subscription } from 'rxjs'; +import { finalize, map, Subscription, switchMap } from 'rxjs'; import { FileService } from '../core/services/file.service'; import { ToggleSidenavService } from '../core/services/toggle-sidenav.service'; import { FileNode, FileNodeInsertAttributeParams, FileNodeInsertParams } from '../models/file-node'; @@ -108,6 +108,7 @@ export class MainComponent implements OnInit, OnDestroy { uploadedProfileSelected: ProfileDescription; private _routeParamsSubscription: Subscription; + private _profileLoadingSubscription: Subscription; constructor( public fileService: FileService, @@ -118,9 +119,6 @@ export class MainComponent implements OnInit, OnDestroy { private loaderService: NgxUiLoaderService, private router: Router, ) { - this.uploadedProfileResponse = this.router.getCurrentNavigation().extras.state as ProfileResponse; - this.uploadedProfileSelected = this.router.getCurrentNavigation().extras.state as ProfileDescription; - this.sideNavService.isOpened.subscribe((status) => { this.opened = status; }); @@ -133,29 +131,20 @@ export class MainComponent implements OnInit, OnDestroy { this.fileService.currentTreeLoaded = false; this._routeParamsSubscription = this.route.params.subscribe((params) => { const profileId = params.id; + // If a profileId has been defined, it is retrieved from backend if (profileId !== undefined) { - if (this.uploadedProfileSelected === undefined) { - this.router.navigate(['/pastis/tenant/1'], { skipLocationChange: false }); - } else { - this.fileService.getProfileAndUpdateTree(this.uploadedProfileSelected); - } + this.loadProfileById(profileId); } else { - // Otherwise we must have an user uploaded profile - this.uploadedProfileResponse.id = null; - - this.loaderService.start(); - this.profileService - .getMetaModel(this.uploadedProfileResponse.sedaVersion) - .pipe( - tap((metaModel) => { - this.sedaService.setMetaModel(metaModel); - this.fileService.linkFileNodeToSedaData(null, [this.uploadedProfileResponse.profile]); - this.fileService.updateTreeWithProfile(this.uploadedProfileResponse); - }), - finalize(() => this.loaderService.stop()), - ) - .subscribe(); + // Check for query params to create a new profile + this.route.queryParams.subscribe((queryParams) => { + if (queryParams['type'] && queryParams['version']) { + this.createNewProfile(queryParams['type'], queryParams['version']); + } else { + // No valid params, redirect to list + this.router.navigate(['/'], { skipLocationChange: false }); + } + }); } }); this.opened = true; @@ -193,6 +182,55 @@ export class MainComponent implements OnInit, OnDestroy { if (this._routeParamsSubscription != null) { this._routeParamsSubscription.unsubscribe(); } + if (this._profileLoadingSubscription != null) { + this._profileLoadingSubscription.unsubscribe(); + } if (this.pendingSub) this.pendingSub.unsubscribe(); } + + private loadProfileById(profileId: string) { + // Unsubscribe from previous profile loading to avoid multiple concurrent requests + if (this._profileLoadingSubscription != null) { + this._profileLoadingSubscription.unsubscribe(); + } + + // Subscribe to profiles list + this._profileLoadingSubscription = this.profileService.retrievedProfiles.subscribe((profiles) => { + if (!profiles || profiles.length === 0) { + // Profiles not loaded yet, refresh the list + this.profileService.refreshListProfiles(); + return; + } + + // Find the profile in the list + const profileDescription = profiles.find((p) => p.id === profileId); + if (profileDescription) { + this.fileService.getProfileAndUpdateTree(profileDescription); + } else { + this.router.navigate(['/'], { skipLocationChange: false }); + } + }); + } + + private createNewProfile(profileType: string, profileVersion: string) { + this.loaderService.start(); + this.profileService + .createProfile('/pastis/profile', profileType as any, profileVersion as any) + .pipe( + tap((profileResponse) => { + this.uploadedProfileResponse = profileResponse; + this.uploadedProfileResponse.id = null; + }), + switchMap((profileResponse) => + this.profileService.getMetaModel(profileResponse.sedaVersion).pipe(map((metaModel) => ({ profileResponse, metaModel }))), + ), + tap(({ profileResponse, metaModel }) => { + this.sedaService.setMetaModel(metaModel); + this.fileService.linkFileNodeToSedaData(null, [profileResponse.profile]); + this.fileService.updateTreeWithProfile(profileResponse); + }), + finalize(() => this.loaderService.stop()), + ) + .subscribe(); + } } diff --git a/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.html b/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.html index 1e62dba9d99..8ec0ab07f69 100644 --- a/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.html +++ b/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.html @@ -37,7 +37,7 @@
{{ 'APPLICATION.PASTIS_APP.TITLE' | translate }}
type="file" /> - + diff --git a/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.ts b/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.ts index 65ac4c2d22b..79605ba594c 100644 --- a/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.ts +++ b/ui/ui-frontend/projects/pastis/src/app/profile/list-profile/list-profile.component.ts @@ -84,7 +84,6 @@ import { ProfileService } from '../../core/services/profile.service'; import { ToggleSidenavService } from '../../core/services/toggle-sidenav.service'; import { BreadcrumbDataTop } from '../../models/breadcrumb'; import { ProfileDescription } from '../../models/profile-description.model'; -import { ProfileResponse } from '../../models/profile-response'; import { DataGeneriquePopupService } from '../../shared/data-generique-popup.service'; import { PastisDialogData } from '../../shared/pastis-dialog/classes/pastis-dialog-data'; import { CreateProfileComponent, CreateProfileFormResult } from '../create-profile/create-profile.component'; @@ -134,9 +133,6 @@ export class ListProfileComponent extends SidenavPage implem sedaUrl: string = this.pastisConfig.pastisPathPrefix + (this.isStandalone ? '' : this.startupService.getTenantIdentifier()) + this.pastisConfig.sedaUrl; - - newProfileUrl: string = this.pastisConfig.pastisNewProfile; - subscription1$: Subscription; _uploadProfileSub: Subscription; subscriptions: Subscription[] = []; @@ -251,8 +247,7 @@ export class ListProfileComponent extends SidenavPage implem editProfile(element: ProfileDescription) { this.profileService.controlSchema.next(element?.controlSchema); - this.router.navigate([this.pastisConfig.pastisEditPage, element.id], { - state: element, + this.router.navigate(['edit', element.id], { relativeTo: this.route, skipLocationChange: false, }); @@ -265,8 +260,11 @@ export class ListProfileComponent extends SidenavPage implem const formData = new FormData(); formData.append('file', fileToUpload, fileToUpload.name); this._uploadProfileSub = this.profileService.uploadProfile(formData).subscribe((response: any) => { - if (response) { - this.router.navigate([this.pastisConfig.pastisNewProfile], { state: response, relativeTo: this.route }); + if (response && response.id) { + // Navigate to edit page with the profile ID + this.router.navigate(['edit', response.id], { + relativeTo: this.route, + }); } }); this.subscriptions.push(this._uploadProfileSub); @@ -289,16 +287,16 @@ export class ListProfileComponent extends SidenavPage implem const dialogRef = this.dialog.open(CreateProfileComponent, createProfileDialogConfig); const subscription = dialogRef .afterClosed() - .pipe( - filter((result) => Boolean(result)), - switchMap((result) => - this.profileService.createProfile(this.pastisConfig.createProfileByTypeUrl, result.profileType, result.profileVersion), - ), - filter((profileResponse) => Boolean(profileResponse)), - ) - .subscribe((profileResponse) => - this.router.navigate([this.pastisConfig.pastisNewProfile], { state: profileResponse, relativeTo: this.route }), - ); + .pipe(filter((result) => Boolean(result))) + .subscribe((result) => { + this.router.navigate(['new'], { + queryParams: { + type: result.profileType, + version: result.profileVersion, + }, + relativeTo: this.route, + }); + }); this.subscriptions.push(subscription); } diff --git a/ui/ui-frontend/projects/pastis/src/app/shared/pastis-popup-option/pastis-popup-option.component.ts b/ui/ui-frontend/projects/pastis/src/app/shared/pastis-popup-option/pastis-popup-option.component.ts index 3571059b8bb..f3a73312aa4 100644 --- a/ui/ui-frontend/projects/pastis/src/app/shared/pastis-popup-option/pastis-popup-option.component.ts +++ b/ui/ui-frontend/projects/pastis/src/app/shared/pastis-popup-option/pastis-popup-option.component.ts @@ -150,8 +150,11 @@ export class PastisPopupOptionComponent implements OnInit, OnDestroy { const formData = new FormData(); formData.append('file', fileToUpload, fileToUpload.name); this.profileService.uploadProfile(formData).subscribe((response: any) => { - if (response) { - this.router.navigate([this.newProfileUrl], { state: response, relativeTo: this.route }); + if (response && response.id) { + // Navigate to edit page with the profile ID + this.router.navigate(['edit', response.id], { + relativeTo: this.route, + }); } }); }