diff --git a/src/app/item-page/bitstreams/upload/upload-bitstream.component.spec.ts b/src/app/item-page/bitstreams/upload/upload-bitstream.component.spec.ts index e85dca2a98d..8bdc05ded81 100644 --- a/src/app/item-page/bitstreams/upload/upload-bitstream.component.spec.ts +++ b/src/app/item-page/bitstreams/upload/upload-bitstream.component.spec.ts @@ -67,6 +67,11 @@ describe('UploadBitstreamComponent', () => { const mockItem = Object.assign(new Item(), { id: 'fake-id', handle: 'fake/handle', + _links: { + self: { + href: '/api/core/items/fake-id' + } + }, metadata: { 'dc.title': [ { @@ -83,6 +88,7 @@ describe('UploadBitstreamComponent', () => { const restEndpoint = 'fake-rest-endpoint'; const mockItemDataService = jasmine.createSpyObj('mockItemDataService', { getBitstreamsEndpoint: observableOf(restEndpoint), + getBundlesEndpoint: observableOf('/api/core/items/fake-id/bundles'), createBundle: createSuccessfulRemoteDataObject$(createdBundle), getBundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [bundle])), }); @@ -131,6 +137,18 @@ describe('UploadBitstreamComponent', () => { it('should navigate the user to the next page', () => { expect(routerStub.navigate).toHaveBeenCalled(); }); + + it('should clear cached requests for the selected bundle bitstreams endpoint', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(restEndpoint); + }); + + it('should clear cached requests for the item bundles endpoint', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('/api/core/items/fake-id/bundles'); + }); + + it('should clear cached requests for the item self endpoint', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith('/api/core/items/fake-id'); + }); }); }); diff --git a/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts b/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts index 56b88806eda..7f825b00f13 100644 --- a/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts +++ b/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts @@ -209,7 +209,28 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy { public onCompleteItem(bitstream) { // Clear cached requests for this bundle's bitstreams to ensure lists on all pages are up-to-date this.bundleService.getBitstreamsEndpoint(this.selectedBundleId).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); + this.requestService.setStaleByHrefSubstring(href); + }); + + // Clear cached requests for this item's bundles to ensure bundle resolution uses fresh data + this.itemService.getBundlesEndpoint(this.itemId).pipe(take(1)).subscribe((href: string) => { + this.requestService.setStaleByHrefSubstring(href); + }); + + // Clear cached requests for this item to ensure breadcrumb navigation resolves a fresh item + this.itemRD$.pipe( + getFirstSucceededRemoteDataPayload(), + take(1), + ).subscribe((item: Item) => { + this.requestService.setStaleByHrefSubstring(item._links.self.href); + + // Clear metadatabitstreams search cache used by preview and CLARIN files sections + this.requestService.setStaleByHrefSubstring('/api/core/metadatabitstreams/search/byHandle'); + if (item?.handle) { + this.requestService.setStaleByHrefSubstring(`handle=${encodeURIComponent(item.handle)}`); + this.requestService.setStaleByHrefSubstring(`handle=${item.handle}`); + } + this.requestService.setStaleByHrefSubstring('fileGrpType=ORIGINAL'); }); // Bring over the item ID as a query parameter diff --git a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts index 08edfe7ac55..9276bbbd7ef 100644 --- a/src/app/item-page/clarin-files-section/clarin-files-section.component.ts +++ b/src/app/item-page/clarin-files-section/clarin-files-section.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; import { Item } from '../../core/shared/item.model'; import { getAllSucceededRemoteListPayload, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { getItemPageRoute } from '../item-page-routing-paths'; @@ -7,14 +7,14 @@ import { RegistryService } from '../../core/registry/registry.service'; import { Router } from '@angular/router'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; @Component({ selector: 'ds-clarin-files-section', templateUrl: './clarin-files-section.component.html', styleUrls: ['./clarin-files-section.component.scss'] }) -export class ClarinFilesSectionComponent implements OnInit { +export class ClarinFilesSectionComponent implements OnInit, OnChanges, OnDestroy { /** * The item to display files for @@ -71,6 +71,9 @@ export class ClarinFilesSectionComponent implements OnInit { */ downloadZipMinFileCount: BehaviorSubject = new BehaviorSubject(-1); + private currentItemHandle: string; + private filesSubscription?: Subscription; + constructor(protected registryService: RegistryService, protected router: Router, @@ -79,17 +82,19 @@ export class ClarinFilesSectionComponent implements OnInit { } ngOnInit(): void { - this.registryService - .getMetadataBitstream(this.itemHandle, 'ORIGINAL') - .pipe(getAllSucceededRemoteListPayload()) - .subscribe((data: MetadataBitstream[]) => { - this.listOfFiles.next(data); - this.generateCurlCommand(); - }); - this.totalFileSizes.next(Number(this.item.firstMetadataValue('local.files.size'))); this.loadDownloadZipConfigProperties(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.item || changes.itemHandle) { + this.refreshFromInputs(true); + } + } + + ngOnDestroy(): void { + this.filesSubscription?.unsubscribe(); + } + setCommandline() { this.isCommandLineVisible = !this.isCommandLineVisible; } @@ -138,4 +143,29 @@ export class ClarinFilesSectionComponent implements OnInit { this.downloadZipMinFileSize.next(Number(config.values[0])); }); } + + private refreshFromInputs(force = false): void { + if (this.item) { + this.totalFileSizes.next(Number(this.item.firstMetadataValue('local.files.size'))); + } + + const handle = this.itemHandle || this.item?.handle; + if (!handle) { + return; + } + + if (!force && handle === this.currentItemHandle) { + return; + } + + this.currentItemHandle = handle; + this.filesSubscription?.unsubscribe(); + this.filesSubscription = this.registryService + .getMetadataBitstream(handle, 'ORIGINAL') + .pipe(getAllSucceededRemoteListPayload()) + .subscribe((data: MetadataBitstream[]) => { + this.listOfFiles.next(data); + this.generateCurlCommand(); + }); + } } diff --git a/src/app/item-page/item.resolver.ts b/src/app/item-page/item.resolver.ts index ffeef57ecb0..c5225675991 100644 --- a/src/app/item-page/item.resolver.ts +++ b/src/app/item-page/item.resolver.ts @@ -52,7 +52,7 @@ export class ItemResolver implements Resolve> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { const itemRD$ = this.itemService.findById(route.params.id, true, - false, + true, ...getItemPageLinksToFollow(), ).pipe( getFirstCompletedRemoteData(), diff --git a/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts b/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts index 1bac02083e7..f2b5341ec01 100644 --- a/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts +++ b/src/app/item-page/simple/field-components/preview-section/preview-section.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BehaviorSubject, of } from 'rxjs'; import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.model'; import { RegistryService } from 'src/app/core/registry/registry.service'; +import { SimpleChange } from '@angular/core'; import { PreviewSectionComponent } from './preview-section.component'; import { ResourceType } from 'src/app/core/shared/resource-type'; @@ -75,7 +76,10 @@ describe('PreviewSectionComponent', () => { expect(component).toBeTruthy(); }); - it('should call getMetadataBitstream on init', () => { + it('should call getMetadataBitstream on item input change', () => { + component.ngOnChanges({ + item: new SimpleChange(undefined, component.item, true), + }); expect(mockRegistryService.getMetadataBitstream).toHaveBeenCalled(); }); diff --git a/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts b/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts index d8365511dbd..73e858da35a 100644 --- a/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts +++ b/src/app/item-page/simple/field-components/preview-section/preview-section.component.ts @@ -1,5 +1,5 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; +import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { MetadataBitstream } from 'src/app/core/metadata/metadata-bitstream.model'; import { RegistryService } from 'src/app/core/registry/registry.service'; import { Item } from 'src/app/core/shared/item.model'; @@ -11,26 +11,53 @@ import { ConfigurationDataService } from '../../../../core/data/configuration-da templateUrl: './preview-section.component.html', styleUrls: ['./preview-section.component.scss'], }) -export class PreviewSectionComponent implements OnInit { +export class PreviewSectionComponent implements OnInit, OnChanges, OnDestroy { @Input() item: Item; listOfFiles: BehaviorSubject = new BehaviorSubject([] as any); emailToContact: string; hasNoFiles: BehaviorSubject = new BehaviorSubject(true); + private currentItemHandle: string; + private filesSubscription?: Subscription; + constructor(protected registryService: RegistryService, - private configService: ConfigurationDataService) {} // Modified + private configService: ConfigurationDataService) {} ngOnInit(): void { - this.registryService - .getMetadataBitstream(this.item.handle, 'ORIGINAL') + this.configService.findByPropertyName('lr.help.mail')?.subscribe(remoteData => { + this.emailToContact = remoteData.payload?.values?.[0]; + }); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.item) { + this.refreshFiles(true); + } + } + + ngOnDestroy(): void { + this.filesSubscription?.unsubscribe(); + } + + private refreshFiles(force = false): void { + const handle = this.item?.handle; + if (!handle) { + return; + } + + if (!force && handle === this.currentItemHandle) { + return; + } + + this.currentItemHandle = handle; + this.filesSubscription?.unsubscribe(); + this.filesSubscription = this.registryService + .getMetadataBitstream(handle, 'ORIGINAL') .pipe(getAllSucceededRemoteListPayload()) .subscribe((data: MetadataBitstream[]) => { this.listOfFiles.next(data); this.hasNoFiles.next(!Array.isArray(data) || data.length === 0); }); - this.configService.findByPropertyName('lr.help.mail')?.subscribe(remoteData => { - this.emailToContact = remoteData.payload?.values?.[0]; - }); } }