diff --git a/src/app/+my-dspace-page/my-dspace-page.component.html b/src/app/+my-dspace-page/my-dspace-page.component.html index 6f1cc41a1e..55d1e304d0 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.html +++ b/src/app/+my-dspace-page/my-dspace-page.component.html @@ -6,6 +6,7 @@ [configurationList]="(configurationList$ | async)" [resultCount]="(resultsRD$ | async)?.payload.totalElements" [viewModeList]="viewModeList" + [refreshFilters]="refreshFilters.asObservable()" [inPlaceSearch]="inPlaceSearch">
@@ -39,7 +41,8 @@
+ [context]="context$ | async" + (contentChange)="onResultsContentChange()">
diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index 0f08795cdc..5ee2a47d9f 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -7,7 +7,7 @@ import { OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { map, switchMap, tap, } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list.model'; @@ -101,6 +101,11 @@ export class MyDSpacePageComponent implements OnInit { */ context$: Observable; + /** + * Emit an event every time search sidebars must refresh their contents. + */ + refreshFilters: Subject = new Subject(); + constructor(private service: SearchService, private sidebarService: SidebarService, private windowService: HostWindowService, @@ -148,6 +153,14 @@ export class MyDSpacePageComponent implements OnInit { } + /** + * Handle the contentChange event from within the my dspace content. + * Notify search sidebars to refresh their content. + */ + onResultsContentChange() { + this.refreshFilters.next(); + } + /** * Set the sidebar to a collapsed state */ @@ -184,5 +197,6 @@ export class MyDSpacePageComponent implements OnInit { if (hasValue(this.sub)) { this.sub.unsubscribe(); } + this.refreshFilters.complete(); } } diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html index 3a829e6ece..2710285f0d 100644 --- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html +++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html @@ -5,7 +5,8 @@ [sortConfig]="searchConfig.sort" [objects]="searchResults" [hideGear]="true" - [context]="context"> + [context]="context" + (contentChange)="contentChange.emit()"> diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts index 35b13c8bae..32b6d9c9f7 100644 --- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts +++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; @@ -41,6 +41,12 @@ export class MyDSpaceResultsComponent { * The current context for the search results */ @Input() context: Context; + + /** + * Emit when one of the results has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * A boolean representing if search results entry are separated by a line */ diff --git a/src/app/core/tasks/claimed-task-data.service.spec.ts b/src/app/core/tasks/claimed-task-data.service.spec.ts index 977590fbed..ab9727592e 100644 --- a/src/app/core/tasks/claimed-task-data.service.spec.ts +++ b/src/app/core/tasks/claimed-task-data.service.spec.ts @@ -11,11 +11,10 @@ import { ClaimedTaskDataService } from './claimed-task-data.service'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; -import { followLink } from '../../shared/utils/follow-link-config.model'; import { getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('ClaimedTaskDataService', () => { let scheduler: TestScheduler; @@ -120,13 +119,7 @@ describe('ClaimedTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', - findListOptions, - followLink('workflowitem', - null, - true, - false, - true)); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions); }); }); }); diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index f2d5c94585..9cfd5a44d6 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -16,7 +16,6 @@ import { CLAIMED_TASK } from './models/claimed-task-object.resource-type'; import { ProcessTaskResponse } from './models/process-task-response'; import { TasksService } from './tasks.service'; import { RemoteData } from '../data/remote-data'; -import { followLink } from '../../shared/utils/follow-link-config.model'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; @@ -116,12 +115,7 @@ export class ClaimedTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem', - null, - true, - false, - true)) - .pipe(getFirstSucceededRemoteData()); + return this.searchTask('findByItem', options).pipe(getFirstSucceededRemoteData()); } } diff --git a/src/app/core/tasks/pool-task-data.service.spec.ts b/src/app/core/tasks/pool-task-data.service.spec.ts index 07fa5a9d66..7279c96e5c 100644 --- a/src/app/core/tasks/pool-task-data.service.spec.ts +++ b/src/app/core/tasks/pool-task-data.service.spec.ts @@ -11,11 +11,10 @@ import { PoolTaskDataService } from './pool-task-data.service'; import { getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { of as observableOf } from 'rxjs/internal/observable/of'; -import { followLink } from '../../shared/utils/follow-link-config.model'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('PoolTaskDataService', () => { let scheduler: TestScheduler; @@ -74,12 +73,7 @@ describe('PoolTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, - followLink('workflowitem', - null, - true, - false, - true)); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions); }); }); diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 83aad10fe5..d44e402e7f 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -15,10 +15,9 @@ import { PoolTask } from './models/pool-task-object.model'; import { POOL_TASK } from './models/pool-task-object.resource-type'; import { TasksService } from './tasks.service'; import { RemoteData } from '../data/remote-data'; -import { followLink } from '../../shared/utils/follow-link-config.model'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; -import { getFirstSucceededRemoteData } from '../shared/operators'; +import { getFirstCompletedRemoteData } from '../shared/operators'; /** * The service handling all REST requests for PoolTask @@ -71,12 +70,7 @@ export class PoolTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem', - null, - true, - false, - true)) - .pipe(getFirstSucceededRemoteData()); + return this.searchTask('findByItem', options).pipe(getFirstCompletedRemoteData()); } /** diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index c24d493d6b..7043191915 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { Component, Injector, OnInit } from '@angular/core'; -import { map, switchMap, tap} from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { DataService } from '../../core/data/data.service'; @@ -64,6 +64,7 @@ export abstract class MyDSpaceReloadableActionsComponent { this.processing$.next(true); return this.actionExecution().pipe( + take(1), switchMap((res: ProcessTaskResponse) => { if (res.hasSucceeded) { return this._reloadObject().pipe( @@ -137,7 +138,7 @@ export abstract class MyDSpaceReloadableActionsComponent { - return dso ? this.convertReloadedObject(dso) : dso; + return dso ? this.convertReloadedObject(dso) : dso; })); } diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index e696170a6f..f2778757ef 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -18,6 +18,7 @@ [importable]="importable" [importConfig]="importConfig" (importObject)="importObject.emit($event)" + (contentChange)="contentChange.emit()" *ngIf="(currentMode$ | async) === viewModeEnum.ListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index ffb5c42880..52881f5eaf 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -53,6 +53,11 @@ export class ObjectCollectionComponent implements OnInit { @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); + /** + * Emit when one of the collection's object has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * Whether or not to add an import button to the object elements */ diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index fb58c2c83d..51468993c1 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,4 +1,14 @@ -import { Component, ComponentFactoryResolver, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + Component, + ComponentFactoryResolver, + ElementRef, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, + EventEmitter +} from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { Context } from '../../../../core/shared/context.model'; @@ -76,6 +86,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy */ @ViewChild('badges', { static: true }) badges: ElementRef; + /** + * Emit when the listable object has been reloaded. + */ + @Output() contentChange = new EventEmitter(); + /** * Whether or not the "Private" badge should be displayed for this listable object */ @@ -141,6 +156,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy componentRef.destroy(); this.object = reloadedObject; this.instantiateComponent(reloadedObject); + this.contentChange.emit(reloadedObject); } }); } diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 4aecaaac8f..331ff1cb28 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -22,7 +22,9 @@ [importConfig]="importConfig" (importObject)="importObject.emit($event)"> + [listID]="selectionConfig?.listId" + (contentChange)="contentChange.emit()" + > diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index b58c8b358e..6f4caae939 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -76,6 +76,11 @@ export class ObjectListComponent { */ @Input() importConfig: { importLabel: string }; + /** + * Emit when one of the listed object has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * The current listable objects */ diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index aaea82df27..2dd810db63 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -7,7 +7,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; import { SearchFiltersComponent } from './search-filters.component'; import { SearchService } from '../../../core/shared/search/search.service'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, Subject } from 'rxjs'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; @@ -66,4 +66,26 @@ describe('SearchFiltersComponent', () => { }); }); + describe('when refreshSearch observable is present and emit events', () => { + + let refreshFiltersEmitter: Subject; + + beforeEach(() => { + spyOn(comp, 'initFilters').and.callFake(() => { /****/}); + + refreshFiltersEmitter = new Subject(); + comp.refreshFilters = refreshFiltersEmitter.asObservable(); + comp.ngOnInit(); + }); + + it('should reinitialize search filters', () => { + + expect(comp.initFilters).toHaveBeenCalledTimes(1); + + refreshFiltersEmitter.next(); + + expect(comp.initFilters).toHaveBeenCalledTimes(2); + }); + }); + }); diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 5daa0f17e0..348af6743d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; @@ -12,17 +12,19 @@ import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; import { Router } from '@angular/router'; +import { hasValue } from '../../empty.util'; @Component({ selector: 'ds-search-filters', styleUrls: ['./search-filters.component.scss'], templateUrl: './search-filters.component.html', + }) /** * This component represents the part of the search sidebar that contains filters. */ -export class SearchFiltersComponent implements OnInit { +export class SearchFiltersComponent implements OnInit, OnDestroy { /** * An observable containing configuration about which filters are shown and how they are shown */ @@ -39,11 +41,18 @@ export class SearchFiltersComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: Observable; + /** * Link to the search page */ searchLink: string; + subs = []; + /** * Initialize instance variables * @param {SearchService} searchService @@ -58,9 +67,12 @@ export class SearchFiltersComponent implements OnInit { } ngOnInit(): void { - this.filters = this.searchConfigService.searchOptions.pipe( - switchMap((options) => this.searchService.getConfig(options.scope, options.configuration).pipe(getFirstSucceededRemoteData())), - ); + + this.initFilters(); + + if (this.refreshFilters) { + this.subs.push(this.refreshFilters.subscribe(() => this.initFilters())); + } this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => { Object.keys(filters).forEach((f) => filters[f] = null); @@ -69,6 +81,12 @@ export class SearchFiltersComponent implements OnInit { this.searchLink = this.getSearchLink(); } + initFilters() { + this.filters = this.searchConfigService.searchOptions.pipe( + switchMap((options) => this.searchService.getConfig(options.scope, options.configuration).pipe(getFirstSucceededRemoteData())), + ); + } + /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ @@ -85,4 +103,12 @@ export class SearchFiltersComponent implements OnInit { trackUpdate(index, config: SearchFilterConfig) { return config ? config.name : undefined; } + + ngOnDestroy() { + this.subs.forEach((sub) => { + if (hasValue(sub)) { + sub.unsubscribe(); + } + }); + } } diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.html b/src/app/shared/search/search-sidebar/search-sidebar.component.html index 638aed7834..74abeadfd8 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.html +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.html @@ -11,7 +11,7 @@ diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 42e8a444bc..2060e0f345 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { SearchConfigurationOption } from '../search-switch-configuration/search-configuration-option.model'; +import { Observable } from 'rxjs'; /** * This component renders a simple item page. @@ -44,6 +45,11 @@ export class SearchSidebarComponent { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: Observable; + /** * Emits event when the user clicks a button to open or close the sidebar */