From 94d034d5479c2d2b130a72b7ef01e7dea21accc0 Mon Sep 17 00:00:00 2001 From: guillermo2519 Date: Wed, 15 Apr 2026 19:29:51 -0600 Subject: [PATCH 1/2] fix #5479: navigate back to correct item path after deleting custom URL --- .../edit-item-page.component.spec.ts | 2 + .../edit-item-page.component.ts | 48 ++++++++++++++++--- src/app/item-page/item-page.resolver.spec.ts | 15 ++++++ src/app/item-page/item-page.resolver.ts | 7 ++- 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts b/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts index 4b5c777ef3e..bdb4a92c5aa 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts @@ -16,6 +16,7 @@ import { RouterModule, RouterStateSnapshot, } from '@angular/router'; +import { ItemDataService } from '@dspace/core/data/item-data.service'; import { Item } from '@dspace/core/shared/item.model'; import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock'; import { createSuccessfulRemoteDataObject } from '@dspace/core/utilities/remote-data.utils'; @@ -92,6 +93,7 @@ describe('EditItemPageComponent', () => { ], providers: [ { provide: ActivatedRoute, useValue: mockRoute }, + { provide: ItemDataService, useValue: { findByHref: () => of(createSuccessfulRemoteDataObject(new Item())) } }, ], schemas: [NO_ERRORS_SCHEMA], }).overrideComponent(EditItemPageComponent, { diff --git a/src/app/item-page/edit-item-page/edit-item-page.component.ts b/src/app/item-page/edit-item-page/edit-item-page.component.ts index 08eebba7feb..75c91cee517 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.component.ts @@ -17,9 +17,10 @@ import { RouterLink, RouterOutlet, } from '@angular/router'; +import { ItemDataService } from '@dspace/core/data/item-data.service'; import { RemoteData } from '@dspace/core/data/remote-data'; -import { getItemPageRoute } from '@dspace/core/router/utils/dso-route.utils'; import { Item } from '@dspace/core/shared/item.model'; +import { getFirstCompletedRemoteData } from '@dspace/core/shared/operators'; import { isNotEmpty } from '@dspace/shared/utils/empty.util'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; @@ -28,7 +29,10 @@ import { Observable, of, } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { + map, + switchMap, +} from 'rxjs/operators'; import { fadeIn, @@ -72,7 +76,12 @@ export class EditItemPageComponent implements OnInit { */ pages: { page: string, enabled: Observable }[]; - constructor(private route: ActivatedRoute, private router: Router, private injector: Injector) { + constructor( + private route: ActivatedRoute, + private router: Router, + private injector: Injector, + private items: ItemDataService, + ) { this.router.events.subscribe(() => this.initPageParamsByRoute()); } @@ -93,8 +102,18 @@ export class EditItemPageComponent implements OnInit { ); } return { page: child.path, enabled: enabled }; - }); // ignore reroutes - this.itemRD$ = this.route.data.pipe(map((data) => data.dso)); + }); + + this.itemRD$ = this.route.data.pipe( + map((data) => data.dso), + switchMap((rd: RemoteData) => { + if (rd?.hasSucceeded && rd?.payload) { + return this.items.findByHref(rd.payload._links.self.href, true, false); + } + return of(rd); + }), + getFirstCompletedRemoteData(), + ); } /** @@ -102,7 +121,24 @@ export class EditItemPageComponent implements OnInit { * @param item The item for which the url is requested */ getItemPage(item: Item): string { - return getItemPageRoute(item); + if (!item) {return null;} + // Extract UUID from current edit URL instead of using item metadata + // This avoids the resolver redirecting back to the (possibly deleted) custom URL + const currentUrl = this.router.url; + const uuidMatch = currentUrl.match(/\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\//); + if (uuidMatch) { + const uuid = uuidMatch[1]; + const type = item.firstMetadataValue('dspace.entity.type'); + if (type) { + return `/entities/${encodeURIComponent(type.toLowerCase())}/${uuid}`; + } + return `/items/${uuid}`; + } + // Fallback + const type = item.firstMetadataValue('dspace.entity.type'); + return type + ? `/entities/${encodeURIComponent(type.toLowerCase())}/${item.uuid}` + : `/items/${item.uuid}`; } /** diff --git a/src/app/item-page/item-page.resolver.spec.ts b/src/app/item-page/item-page.resolver.spec.ts index 0d2132239f6..c71dc8eb774 100644 --- a/src/app/item-page/item-page.resolver.spec.ts +++ b/src/app/item-page/item-page.resolver.spec.ts @@ -177,6 +177,21 @@ describe('itemPageResolver', () => { }); }); + it('should not navigate to custom URL if coming from edit page', (done) => { + spyOn(router, 'navigateByUrl').and.callThrough(); + Object.defineProperty(router, 'url', { get: () => `/entities/person/${uuid}/edit`, configurable: true }); + + const route = { params: { id: uuid } } as any; + const state = { url: `/entities/person/${uuid}` } as any; + + resolver(route, state, router, itemService, store, authService, platformId, hardRedirectService) + .pipe(first()) + .subscribe(() => { + expect(router.navigateByUrl).not.toHaveBeenCalled(); + done(); + }); + }); + it('should not navigate if dspace.customurl matches the current route id', (done) => { spyOn(router, 'navigateByUrl').and.callThrough(); diff --git a/src/app/item-page/item-page.resolver.ts b/src/app/item-page/item-page.resolver.ts index 58f690ae309..262f676cc2a 100644 --- a/src/app/item-page/item-page.resolver.ts +++ b/src/app/item-page/item-page.resolver.ts @@ -77,7 +77,12 @@ export const itemPageResolver: ResolveFn> = ( itemRoute = isSubPath ? state.url : router.parseUrl(getItemPageRoute(rd.payload)).toString(); let newUrl: string; if (route.params.id !== customUrl && !isSubPath) { - newUrl = itemRoute.replace(route.params.id,rd.payload.firstMetadataValue('dspace.customurl')); + // Only redirect to custom URL if navigating directly to item page (not from edit/administer) + const referer = router.url; + const isComingFromEdit = referer.includes('/edit'); + if (!isComingFromEdit) { + newUrl = itemRoute.replace(route.params.id, rd.payload.firstMetadataValue('dspace.customurl')); + } } else if (isSubPath && route.params.id === customUrl) { // In case of a sub path, we need to ensure we navigate to the edit page of the item ID, not the custom URL const itemId = rd.payload.uuid; From 2253d1e301f50d3335be6c234bf4dba17cce0088 Mon Sep 17 00:00:00 2001 From: guillermo2519 Date: Wed, 15 Apr 2026 19:59:20 -0600 Subject: [PATCH 2/2] fix #5479: navigate back to correct fix ngOnInit --- src/app/item-page/edit-item-page/edit-item-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/edit-item-page.component.ts b/src/app/item-page/edit-item-page/edit-item-page.component.ts index 75c91cee517..95d568f8137 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.component.ts @@ -107,7 +107,7 @@ export class EditItemPageComponent implements OnInit { this.itemRD$ = this.route.data.pipe( map((data) => data.dso), switchMap((rd: RemoteData) => { - if (rd?.hasSucceeded && rd?.payload) { + if (rd?.hasSucceeded && rd?.payload?._links?.self?.href) { return this.items.findByHref(rd.payload._links.self.href, true, false); } return of(rd);