[CSTPER-3620] Fixed workspace followlink and search filters update

This commit is contained in:
Alessandro Martelli
2021-02-22 16:56:29 +01:00
parent c78cd9ad71
commit 523b7a497c
18 changed files with 130 additions and 47 deletions

View File

@@ -6,6 +6,7 @@
[configurationList]="(configurationList$ | async)"
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
[viewModeList]="viewModeList"
[refreshFilters]="refreshFilters.asObservable()"
[inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
<div class="col-12 col-md-9">
<ds-search-form id="search-form"
@@ -26,6 +27,7 @@
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
(toggleSidebar)="closeSidebar()"
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"
[refreshFilters]="refreshFilters.asObservable()"
[inPlaceSearch]="inPlaceSearch">
</ds-search-sidebar>
<div id="search-content" class="col-12">
@@ -39,7 +41,8 @@
</div>
<ds-my-dspace-results [searchResults]="resultsRD$ | async"
[searchConfig]="searchOptions$ | async"
[context]="context$ | async"></ds-my-dspace-results>
[context]="context$ | async"
(contentChange)="onResultsContentChange()"></ds-my-dspace-results>
</div>
</div>
</div>

View File

@@ -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<Context>;
/**
* Emit an event every time search sidebars must refresh their contents.
*/
refreshFilters: Subject<any> = new Subject<any>();
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();
}
}

View File

@@ -5,7 +5,8 @@
[sortConfig]="searchConfig.sort"
[objects]="searchResults"
[hideGear]="true"
[context]="context">
[context]="context"
(contentChange)="contentChange.emit()">
</ds-viewable-collection>
</div>
<ds-loading *ngIf="isLoading()" message="{{'loading.mydspace-results' | translate}}"></ds-loading>

View File

@@ -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<any>();
/**
* A boolean representing if search results entry are separated by a line
*/

View File

@@ -11,7 +11,6 @@ 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';
@@ -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);
});
});
});

View File

@@ -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<ClaimedTask> {
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());
}
}

View File

@@ -11,7 +11,6 @@ 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';
@@ -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);
});
});

View File

@@ -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<PoolTask> {
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());
}
/**

View File

@@ -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<T extends DSpaceObject,
startActionExecution(): Observable<DSpaceObject> {
this.processing$.next(true);
return this.actionExecution().pipe(
take(1),
switchMap((res: ProcessTaskResponse) => {
if (res.hasSucceeded) {
return this._reloadObject().pipe(

View File

@@ -18,6 +18,7 @@
[importable]="importable"
[importConfig]="importConfig"
(importObject)="importObject.emit($event)"
(contentChange)="contentChange.emit()"
*ngIf="(currentMode$ | async) === viewModeEnum.ListElement">
</ds-object-list>

View File

@@ -53,6 +53,11 @@ export class ObjectCollectionComponent implements OnInit {
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
/**
* Emit when one of the collection's object has changed.
*/
@Output() contentChange = new EventEmitter<any>();
/**
* Whether or not to add an import button to the object elements
*/

View File

@@ -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<ListableObject>();
/**
* 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);
}
});
}

View File

@@ -22,7 +22,9 @@
[importConfig]="importConfig"
(importObject)="importObject.emit($event)"></ds-importable-list-item-control>
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [index]="i" [context]="context" [linkType]="linkType"
[listID]="selectionConfig?.listId"></ds-listable-object-component-loader>
[listID]="selectionConfig?.listId"
(contentChange)="contentChange.emit()"
></ds-listable-object-component-loader>
</li>
</ul>
</ds-pagination>

View File

@@ -76,6 +76,11 @@ export class ObjectListComponent {
*/
@Input() importConfig: { importLabel: string };
/**
* Emit when one of the listed object has changed.
*/
@Output() contentChange = new EventEmitter<any>();
/**
* The current listable objects
*/

View File

@@ -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<any>;
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);
});
});
});

View File

@@ -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<any>;
/**
* 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();
}
});
}
}

View File

@@ -11,7 +11,7 @@
<ds-view-mode-switch *ngIf="showViewModes" [viewModeList]="viewModeList" class="d-none d-md-block"></ds-view-mode-switch>
<div class="sidebar-content">
<ds-search-switch-configuration [inPlaceSearch]="inPlaceSearch" *ngIf="configurationList" [configurationList]="configurationList"></ds-search-switch-configuration>
<ds-search-filters [inPlaceSearch]="inPlaceSearch"></ds-search-filters>
<ds-search-filters [refreshFilters]="refreshFilters" [inPlaceSearch]="inPlaceSearch"></ds-search-filters>
<ds-search-settings></ds-search-settings>
</div>
</div>

View File

@@ -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<any>;
/**
* Emits event when the user clicks a button to open or close the sidebar
*/