mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
fixed collection page, added SearchFilter Model
This commit is contained in:
@@ -5,7 +5,6 @@ import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { PaginatedList } from '../core/data/paginated-list';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
|
||||
@@ -21,8 +20,8 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
|
||||
import { filter, flatMap, map } from 'rxjs/operators';
|
||||
import { SearchService } from '../+search-page/search-service/search.service';
|
||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||
import { SearchResult } from '../+search-page/search-result.model';
|
||||
import { toDSpaceObjectListRD } from '../core/shared/operators';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page',
|
||||
@@ -68,18 +67,13 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
this.metadata.processRemoteData(this.collectionRD$);
|
||||
const page = +params.page || this.paginationConfig.currentPage;
|
||||
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||
const sortDirection = +params.page || this.sortConfig.direction;
|
||||
const pagination = Object.assign({},
|
||||
this.paginationConfig,
|
||||
{ currentPage: page, pageSize: pageSize }
|
||||
);
|
||||
const sort = Object.assign({},
|
||||
this.sortConfig,
|
||||
{ direction: sortDirection, field: params.sortField }
|
||||
);
|
||||
this.updatePage({
|
||||
pagination: pagination,
|
||||
sort: sort
|
||||
sort: this.sortConfig
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -91,7 +85,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
scope: this.collectionId,
|
||||
pagination: searchOptions.pagination,
|
||||
sort: searchOptions.sort,
|
||||
filters: {type: 2}
|
||||
dsoType: DSpaceObjectType.ITEM
|
||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="itemRD?.payload as item">
|
||||
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
||||
<div class="simple-view-link">
|
||||
<a class="btn btn-outline-primary col-4" [routerLink]="['/items/' + item.id]">
|
||||
<div class="simple-view-link my-3">
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||
{{"item.page.link.simple" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -1,7 +1,10 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
:host {
|
||||
div.simple-view-link {
|
||||
text-align: center;
|
||||
margin: 20px;
|
||||
a {
|
||||
min-width: 25%;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,17 +2,19 @@ import 'rxjs/add/observable/of';
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
import { SearchFilter } from './search-filter.model';
|
||||
|
||||
describe('PaginatedSearchOptions', () => {
|
||||
let options: PaginatedSearchOptions;
|
||||
const sortOptions = new SortOptions('test.field', SortDirection.DESC);
|
||||
const pageOptions = Object.assign(new PaginationComponentOptions(), { pageSize: 40, page: 1 });
|
||||
const filters = { 'f.test': ['value'], 'f.example': ['another value', 'second value'] };
|
||||
const filters = [new SearchFilter('f.test', ['value']), new SearchFilter('f.example', ['another value', 'second value'])];
|
||||
const query = 'search query';
|
||||
const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47';
|
||||
const baseUrl = 'www.rest.com';
|
||||
beforeEach(() => {
|
||||
options = new PaginatedSearchOptions({sort: sortOptions, pagination: pageOptions, filters: filters, query: query, scope: scope});
|
||||
options = new PaginatedSearchOptions({sort: sortOptions, pagination: pageOptions, filters: filters, query: query, scope: scope, dsoType: DSpaceObjectType.ITEM});
|
||||
});
|
||||
|
||||
describe('when toRestUrl is called', () => {
|
||||
@@ -25,6 +27,7 @@ describe('PaginatedSearchOptions', () => {
|
||||
'size=40&' +
|
||||
'query=search query&' +
|
||||
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
|
||||
'dsoType=ITEM&' +
|
||||
'f.test=value,query&' +
|
||||
'f.example=another value,query&' +
|
||||
'f.example=second value,query'
|
||||
|
@@ -2,6 +2,8 @@ import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { SearchOptions } from './search-options.model';
|
||||
import { SearchFilter } from './search-filter.model';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
|
||||
/**
|
||||
* This model class represents all parameters needed to request information about a certain page of a search request, in a certain order
|
||||
@@ -10,8 +12,8 @@ export class PaginatedSearchOptions extends SearchOptions {
|
||||
pagination?: PaginationComponentOptions;
|
||||
sort?: SortOptions;
|
||||
|
||||
constructor(options: {scope?: string, query?: string, filters?: any, pagination?: PaginationComponentOptions, sort?: SortOptions}) {
|
||||
super(options)
|
||||
constructor(options: {scope?: string, query?: string, dsoType?: DSpaceObjectType, filters?: SearchFilter[], pagination?: PaginationComponentOptions, sort?: SortOptions}) {
|
||||
super(options);
|
||||
this.pagination = options.pagination;
|
||||
this.sort = options.sort;
|
||||
}
|
||||
|
20
src/app/+search-page/search-filter.model.ts
Normal file
20
src/app/+search-page/search-filter.model.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Represents a search filter
|
||||
*/
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
export class SearchFilter {
|
||||
key: string;
|
||||
values: string[];
|
||||
operator: string;
|
||||
|
||||
constructor(key: string, values: string[], operator?: string) {
|
||||
this.key = key;
|
||||
this.values = values;
|
||||
if (hasValue(operator)) {
|
||||
this.operator = operator;
|
||||
} else {
|
||||
this.operator = 'query';
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,17 @@
|
||||
import 'rxjs/add/observable/of';
|
||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||
import { SearchOptions } from './search-options.model';
|
||||
import { SearchFilter } from './search-filter.model';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
|
||||
describe('SearchOptions', () => {
|
||||
let options: PaginatedSearchOptions;
|
||||
const filters = { 'f.test': ['value'], 'f.example': ['another value', 'second value'] };
|
||||
const filters = [new SearchFilter('f.test', ['value']), new SearchFilter('f.example', ['another value', 'second value'])];
|
||||
const query = 'search query';
|
||||
const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47';
|
||||
const baseUrl = 'www.rest.com';
|
||||
beforeEach(() => {
|
||||
options = new SearchOptions({filters: filters, query: query, scope: scope});
|
||||
options = new SearchOptions({ filters: filters, query: query, scope: scope , dsoType: DSpaceObjectType.ITEM});
|
||||
});
|
||||
|
||||
describe('when toRestUrl is called', () => {
|
||||
@@ -19,6 +21,7 @@ describe('SearchOptions', () => {
|
||||
expect(outcome).toEqual('www.rest.com?' +
|
||||
'query=search query&' +
|
||||
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
|
||||
'dsoType=ITEM&' +
|
||||
'f.test=value,query&' +
|
||||
'f.example=another value,query&' +
|
||||
'f.example=second value,query'
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
import 'core-js/library/fn/object/entries';
|
||||
import { SearchFilter } from './search-filter.model';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
|
||||
/**
|
||||
* This model class represents all parameters needed to request information about a certain search request
|
||||
@@ -8,11 +10,13 @@ import 'core-js/library/fn/object/entries';
|
||||
export class SearchOptions {
|
||||
scope?: string;
|
||||
query?: string;
|
||||
filters?: any;
|
||||
dsoType?: DSpaceObjectType;
|
||||
filters?: SearchFilter[];
|
||||
|
||||
constructor(options: {scope?: string, query?: string, filters?: any}) {
|
||||
constructor(options: {scope?: string, query?: string, dsoType?: DSpaceObjectType, filters?: SearchFilter[]}) {
|
||||
this.scope = options.scope;
|
||||
this.query = options.query;
|
||||
this.dsoType = options.dsoType;
|
||||
this.filters = options.filters;
|
||||
}
|
||||
|
||||
@@ -27,13 +31,15 @@ export class SearchOptions {
|
||||
if (isNotEmpty(this.query)) {
|
||||
args.push(`query=${this.query}`);
|
||||
}
|
||||
|
||||
if (isNotEmpty(this.scope)) {
|
||||
args.push(`scope=${this.scope}`);
|
||||
}
|
||||
if (isNotEmpty(this.dsoType)) {
|
||||
args.push(`dsoType=${this.dsoType}`);
|
||||
}
|
||||
if (isNotEmpty(this.filters)) {
|
||||
Object.entries(this.filters).forEach(([key, values]) => {
|
||||
values.forEach((value) => args.push(`${key}=${value},query`));
|
||||
this.filters.forEach((filter: SearchFilter) => {
|
||||
filter.values.forEach((value) => args.push(`${filter.key}=${value},${filter.operator}`));
|
||||
});
|
||||
}
|
||||
if (isNotEmpty(args)) {
|
||||
|
@@ -178,4 +178,4 @@ describe('SearchPageComponent', () => {
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})
|
||||
|
@@ -4,11 +4,11 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SearchFilter } from '../search-filter.model';
|
||||
|
||||
describe('SearchConfigurationService', () => {
|
||||
let service: SearchConfigurationService;
|
||||
const value1 = 'random value';
|
||||
const value2 = 'another value';
|
||||
const prefixFilter = {
|
||||
'f.author': ['another value'],
|
||||
'f.date.min': ['2013'],
|
||||
@@ -20,10 +20,11 @@ describe('SearchConfigurationService', () => {
|
||||
query: '',
|
||||
scope: ''
|
||||
});
|
||||
const backendFilters = { 'f.author': ['another value'], 'f.date': ['[2013 TO 2018]'] };
|
||||
|
||||
const backendFilters = [new SearchFilter('f.author', ['another value']), new SearchFilter('f.date', ['[2013 TO 2018]'])];
|
||||
|
||||
const spy = jasmine.createSpyObj('RouteService', {
|
||||
getQueryParameterValue: Observable.of([value1, value2]),
|
||||
getQueryParameterValue: Observable.of(value1),
|
||||
getQueryParamsWithPrefix: Observable.of(prefixFilter)
|
||||
});
|
||||
|
||||
@@ -51,6 +52,15 @@ describe('SearchConfigurationService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getCurrentDSOType is called', () => {
|
||||
beforeEach(() => {
|
||||
service.getCurrentDSOType();
|
||||
});
|
||||
it('should call getQueryParameterValue on the routeService with parameter name \'dsoType\'', () => {
|
||||
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('dsoType');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getCurrentFrontendFilters is called', () => {
|
||||
beforeEach(() => {
|
||||
service.getCurrentFrontendFilters();
|
||||
@@ -101,6 +111,7 @@ describe('SearchConfigurationService', () => {
|
||||
spyOn(service, 'getCurrentSort').and.callThrough();
|
||||
spyOn(service, 'getCurrentScope').and.callThrough();
|
||||
spyOn(service, 'getCurrentQuery').and.callThrough();
|
||||
spyOn(service, 'getCurrentDSOType').and.callThrough();
|
||||
spyOn(service, 'getCurrentFilters').and.callThrough();
|
||||
});
|
||||
|
||||
@@ -113,6 +124,7 @@ describe('SearchConfigurationService', () => {
|
||||
expect(service.getCurrentSort).not.toHaveBeenCalled();
|
||||
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||
expect(service.getCurrentDSOType).toHaveBeenCalled();
|
||||
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -126,6 +138,7 @@ describe('SearchConfigurationService', () => {
|
||||
expect(service.getCurrentSort).toHaveBeenCalled();
|
||||
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||
expect(service.getCurrentDSOType).toHaveBeenCalled();
|
||||
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -6,11 +6,13 @@ import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { RouteService } from '../../shared/services/route.service';
|
||||
import { hasNoValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
||||
import { SearchFilter } from '../search-filter.model';
|
||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||
|
||||
/**
|
||||
* Service that performs all actions that have to do with the current search configuration
|
||||
@@ -99,6 +101,15 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<number>} Emits the current DSpaceObject type as a number
|
||||
*/
|
||||
getCurrentDSOType(): Observable<DSpaceObjectType> {
|
||||
return this.routeService.getQueryParameterValue('dsoType')
|
||||
.filter((type) => hasValue(type) && hasValue(DSpaceObjectType[type.toUpperCase()]))
|
||||
.map((type) => DSpaceObjectType[type.toUpperCase()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<string>} Emits the current pagination settings
|
||||
*/
|
||||
@@ -133,25 +144,25 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
/**
|
||||
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend
|
||||
*/
|
||||
getCurrentFilters(): Observable<Params> {
|
||||
getCurrentFilters(): Observable<SearchFilter[]> {
|
||||
return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => {
|
||||
if (isNotEmpty(filterParams)) {
|
||||
const params = {};
|
||||
const filters = [];
|
||||
Object.keys(filterParams).forEach((key) => {
|
||||
if (key.endsWith('.min') || key.endsWith('.max')) {
|
||||
const realKey = key.slice(0, -4);
|
||||
if (isEmpty(params[realKey])) {
|
||||
if (hasNoValue(filters.find((filter) => filter.key === realKey))) {
|
||||
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
|
||||
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
|
||||
params[realKey] = ['[' + min + ' TO ' + max + ']'];
|
||||
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']']));
|
||||
}
|
||||
} else {
|
||||
params[key] = filterParams[key];
|
||||
filters.push(new SearchFilter(key, filterParams[key]));
|
||||
}
|
||||
});
|
||||
return params;
|
||||
return filters;
|
||||
}
|
||||
return filterParams;
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,6 +182,7 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
return Observable.merge(
|
||||
this.getScopePart(defaults.scope),
|
||||
this.getQueryPart(defaults.query),
|
||||
this.getDSOTypePart(),
|
||||
this.getFiltersPart()
|
||||
).subscribe((update) => {
|
||||
const currentValue: SearchOptions = this.searchOptions.getValue();
|
||||
@@ -190,6 +202,7 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
this.getSortPart(defaults.sort),
|
||||
this.getScopePart(defaults.scope),
|
||||
this.getQueryPart(defaults.query),
|
||||
this.getDSOTypePart(),
|
||||
this.getFiltersPart()
|
||||
).subscribe((update) => {
|
||||
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
|
||||
@@ -241,6 +254,15 @@ export class SearchConfigurationService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
|
||||
*/
|
||||
private getDSOTypePart(): Observable<any> {
|
||||
return this.getCurrentDSOType().map((dsoType) => {
|
||||
return { dsoType }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object
|
||||
*/
|
||||
|
7
src/app/core/shared/dspace-object-type.model.ts
Normal file
7
src/app/core/shared/dspace-object-type.model.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum DSpaceObjectType {
|
||||
BUNDLE = 'BUNDLE',
|
||||
BITSTREAM = 'BITSTREAM',
|
||||
ITEM = 'ITEM',
|
||||
COLLECTION = 'COLLECTION',
|
||||
COMMUNITY = 'COMMUNITY',
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
<div class="thumbnail">
|
||||
<img *ngIf="thumbnail" [src]="thumbnail.content" (error)="errorHandler($event)"/>
|
||||
<img *ngIf="!thumbnail" [src]="holderSource | dsSafeUrl"/>
|
||||
<img *ngIf="thumbnail" [src]="thumbnail.content" (error)="errorHandler($event)" class="img-fluid"/>
|
||||
<img *ngIf="!thumbnail" [src]="holderSource | dsSafeUrl" class="img-fluid"/>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user