mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
[CSTPER-3620] Fixed workspace followlink and search filters update
This commit is contained in:
@@ -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>
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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(
|
||||
|
@@ -18,6 +18,7 @@
|
||||
[importable]="importable"
|
||||
[importConfig]="importConfig"
|
||||
(importObject)="importObject.emit($event)"
|
||||
(contentChange)="contentChange.emit()"
|
||||
*ngIf="(currentMode$ | async) === viewModeEnum.ListElement">
|
||||
</ds-object-list>
|
||||
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
*/
|
||||
|
Reference in New Issue
Block a user