Merge pull request #531 from atmire/clean-relationships-in-submission

Create relationships during the submission
This commit is contained in:
Tim Donohue
2019-12-20 13:34:32 -06:00
committed by GitHub
358 changed files with 6719 additions and 1616 deletions

View File

@@ -15,7 +15,11 @@ module.exports = function (config) {
}; };
var configuration = { var configuration = {
client: {
jasmine: {
random: false
}
},
// base path that will be used to resolve all patterns (e.g. files, exclude) // base path that will be used to resolve all patterns (e.g. files, exclude)
basePath: '', basePath: '',

View File

@@ -140,6 +140,7 @@
"text-mask-core": "5.0.1", "text-mask-core": "5.0.1",
"ts-loader": "^5.2.1", "ts-loader": "^5.2.1",
"ts-md5": "^1.2.4", "ts-md5": "^1.2.4",
"url-parse": "^1.4.7",
"uuid": "^3.2.1", "uuid": "^3.2.1",
"webfontloader": "1.6.28", "webfontloader": "1.6.28",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.0",

View File

@@ -459,6 +459,9 @@
"footer.link.duraspace": "DuraSpace", "footer.link.duraspace": "DuraSpace",
"form.add": "Add",
"form.add-help": "Click here to add the current entry and to add another one",
"form.cancel": "Cancel", "form.cancel": "Cancel",
@@ -484,6 +487,10 @@
"form.loading": "Loading...", "form.loading": "Loading...",
"form.lookup": "Lookup",
"form.lookup-help": "Click here to look up an existing relation",
"form.no-results": "No results found", "form.no-results": "No results found",
"form.no-value": "No value entered", "form.no-value": "No value entered",
@@ -1361,6 +1368,9 @@
"search.filters.applied.f.subject": "Subject", "search.filters.applied.f.subject": "Subject",
"search.filters.applied.f.submitter": "Submitter", "search.filters.applied.f.submitter": "Submitter",
"search.filters.applied.f.jobTitle": "Job Title",
"search.filters.applied.f.birthDate.max": "End birth date",
"search.filters.applied.f.birthDate.min": "Start birth date",
@@ -1529,15 +1539,69 @@
"submission.general.save-later": "Save for later", "submission.general.save-later": "Save for later",
"submission.sections.describe.relationship-lookup.close": "Close",
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
"submission.sections.describe.relationship-lookup.search-tab.loading": "Loading...",
"submission.sections.describe.relationship-lookup.search-tab.placeholder": "Search query",
"submission.sections.describe.relationship-lookup.search-tab.search": "Go",
"submission.sections.describe.relationship-lookup.search-tab.select-all": "Select all",
"submission.sections.describe.relationship-lookup.search-tab.select-page": "Select page",
"submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Author": "Search for Authors",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Search for Journals",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Issue": "Search for Journal Issues",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies",
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding": "Search for Funding", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding": "Search for Funding",
"submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})",
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
"submission.sections.describe.relationship-lookup.title.Journal Volume": "Journal Volumes",
"submission.sections.describe.relationship-lookup.title.Journal": "Journals",
"submission.sections.describe.relationship-lookup.title.Author": "Authors",
"submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency", "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency",
"submission.sections.describe.relationship-lookup.title.Funding": "Funding", "submission.sections.describe.relationship-lookup.title.Funding": "Funding",
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
"submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Your selection is currently empty.",
"submission.sections.describe.relationship-lookup.selection-tab.title.Author": "Selected Authors",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Volume": "Selected Journal Volume",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Issue": "Selected Issue",
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.",
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
"submission.sections.describe.relationship-lookup.name-variant.notification.decline": "Use only for this submission",
"submission.sections.general.add-more": "Add more", "submission.sections.general.add-more": "Add more",

View File

@@ -13,7 +13,6 @@ import { combineLatest as combineLatestObservable } from 'rxjs';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model'; import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';

View File

@@ -1,29 +1,23 @@
import { CollectionItemMapperComponent } from './collection-item-mapper.component'; import { CollectionItemMapperComponent } from './collection-item-mapper.component';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { SearchFormComponent } from '../../shared/search-form/search-form.component'; import { SearchFormComponent } from '../../shared/search-form/search-form.component';
import { SearchPageModule } from '../../+search-page/search-page.module';
import { ObjectCollectionComponent } from '../../shared/object-collection/object-collection.component';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
import { RouterStub } from '../../shared/testing/router-stub'; import { RouterStub } from '../../shared/testing/router-stub';
import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service';
import { SearchService } from '../../+search-page/search-service/search.service';
import { SearchServiceStub } from '../../shared/testing/search-service-stub'; import { SearchServiceStub } from '../../shared/testing/search-service-stub';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
import { ItemDataService } from '../../core/data/item-data.service'; import { ItemDataService } from '../../core/data/item-data.service';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { SharedModule } from '../../shared/shared.module';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { EventEmitter, NgModule } from '@angular/core'; import { EventEmitter } from '@angular/core';
import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@@ -36,13 +30,14 @@ import { ItemSelectComponent } from '../../shared/object-select/item-select/item
import { ObjectSelectService } from '../../shared/object-select/object-select.service'; import { ObjectSelectService } from '../../shared/object-select/object-select.service';
import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub'; import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub';
import { VarDirective } from '../../shared/utils/var.directive'; import { VarDirective } from '../../shared/utils/var.directive';
import { Observable } from 'rxjs/internal/Observable';
import { of as observableOf, of } from 'rxjs/internal/observable/of'; import { of as observableOf, of } from 'rxjs/internal/observable/of';
import { RestResponse } from '../../core/cache/response.models'; import { RestResponse } from '../../core/cache/response.models';
import { SearchFixedFilterService } from '../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { RouteService } from '../../core/services/route.service'; import { RouteService } from '../../core/services/route.service';
import { ErrorComponent } from '../../shared/error/error.component'; import { ErrorComponent } from '../../shared/error/error.component';
import { LoadingComponent } from '../../shared/loading/loading.component'; import { LoadingComponent } from '../../shared/loading/loading.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { SearchService } from '../../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
describe('CollectionItemMapperComponent', () => { describe('CollectionItemMapperComponent', () => {
let comp: CollectionItemMapperComponent; let comp: CollectionItemMapperComponent;
@@ -135,7 +130,6 @@ describe('CollectionItemMapperComponent', () => {
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
{ provide: RouteService, useValue: routeServiceStub }, { provide: RouteService, useValue: routeServiceStub },
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
] ]
}).compileComponents(); }).compileComponents();
})); }));

View File

@@ -5,12 +5,9 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service';
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
import { map, startWith, switchMap, take, tap } from 'rxjs/operators'; import { map, startWith, switchMap, take } from 'rxjs/operators';
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators';
import { SearchService } from '../../+search-page/search-service/search.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
@@ -22,6 +19,9 @@ import { isNotEmpty } from '../../shared/empty.util';
import { RestResponse } from '../../core/cache/response.models'; import { RestResponse } from '../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { SearchService } from '../../core/shared/search/search.service';
@Component({ @Component({
selector: 'ds-collection-item-mapper', selector: 'ds-collection-item-mapper',

View File

@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs'; import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators'; import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchService } from '../+search-page/search-service/search.service'; import { SearchService } from '../core/shared/search/search.service';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { CollectionDataService } from '../core/data/collection-data.service'; import { CollectionDataService } from '../core/data/collection-data.service';
import { PaginatedList } from '../core/data/paginated-list'; import { PaginatedList } from '../core/data/paginated-list';

View File

@@ -9,9 +9,8 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c
import { CollectionFormComponent } from './collection-form/collection-form.component'; import { CollectionFormComponent } from './collection-form/collection-form.component';
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
import { SearchService } from '../+search-page/search-service/search.service';
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service'; import { SearchService } from '../core/shared/search/search.service';
import { StatisticsModule } from '../statistics/statistics.module'; import { StatisticsModule } from '../statistics/statistics.module';
@NgModule({ @NgModule({
@@ -31,7 +30,6 @@ import { StatisticsModule } from '../statistics/statistics.module';
], ],
providers: [ providers: [
SearchService, SearchService,
SearchFixedFilterService
] ]
}) })
export class CollectionPageModule { export class CollectionPageModule {

View File

@@ -1,15 +1,12 @@
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ItemCollectionMapperComponent } from './item-collection-mapper.component'; import { ItemCollectionMapperComponent } from './item-collection-mapper.component';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service';
import { SearchService } from '../../../+search-page/search-service/search.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { RouterStub } from '../../../shared/testing/router-stub'; import { RouterStub } from '../../../shared/testing/router-stub';
@@ -19,7 +16,6 @@ import { SearchServiceStub } from '../../../shared/testing/search-service-stub';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { PageInfo } from '../../../core/shared/page-info.model'; import { PageInfo } from '../../../core/shared/page-info.model';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { SharedModule } from '../../../shared/shared.module';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowService } from '../../../shared/host-window.service';
@@ -28,7 +24,6 @@ import { By } from '@angular/platform-browser';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { ObjectSelectService } from '../../../shared/object-select/object-select.service'; import { ObjectSelectService } from '../../../shared/object-select/object-select.service';
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub'; import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub';
import { Observable } from 'rxjs/internal/Observable';
import { of } from 'rxjs/internal/observable/of'; import { of } from 'rxjs/internal/observable/of';
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { CollectionSelectComponent } from '../../../shared/object-select/collection-select/collection-select.component'; import { CollectionSelectComponent } from '../../../shared/object-select/collection-select/collection-select.component';
@@ -39,6 +34,9 @@ import { SearchFormComponent } from '../../../shared/search-form/search-form.com
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { ErrorComponent } from '../../../shared/error/error.component'; import { ErrorComponent } from '../../../shared/error/error.component';
import { LoadingComponent } from '../../../shared/loading/loading.component'; import { LoadingComponent } from '../../../shared/loading/loading.component';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
describe('ItemCollectionMapperComponent', () => { describe('ItemCollectionMapperComponent', () => {
let comp: ItemCollectionMapperComponent; let comp: ItemCollectionMapperComponent;

View File

@@ -2,15 +2,12 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators'; import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { SearchService } from '../../../+search-page/search-service/search.service';
import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service';
import { map, startWith, switchMap, take } from 'rxjs/operators'; import { map, startWith, switchMap, take } from 'rxjs/operators';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@@ -19,6 +16,9 @@ import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'
import { isNotEmpty } from '../../../shared/empty.util'; import { isNotEmpty } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service';
@Component({ @Component({
selector: 'ds-item-collection-mapper', selector: 'ds-item-collection-mapper',

View File

@@ -9,7 +9,6 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ItemMoveComponent } from './item-move.component'; import { ItemMoveComponent } from './item-move.component';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { SearchService } from '../../../+search-page/search-service/search.service';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
@@ -18,6 +17,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { SearchService } from '../../../core/shared/search/search.service';
describe('ItemMoveComponent', () => { describe('ItemMoveComponent', () => {
let comp: ItemMoveComponent; let comp: ItemMoveComponent;

View File

@@ -1,12 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { SearchService } from '../../../+search-page/search-service/search.service';
import { first, map } from 'rxjs/operators'; import { first, map } from 'rxjs/operators';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { SearchOptions } from '../../../+search-page/search-options.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { SearchResult } from '../../../+search-page/search-result.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
@@ -17,9 +14,10 @@ import { getItemEditPath } from '../../item-page-routing.module';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { tap } from 'rxjs/internal/operators/tap';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; import { SearchService } from '../../../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchResult } from '../../../shared/search/search-result.model';
@Component({ @Component({
selector: 'ds-item-move', selector: 'ds-item-move',

View File

@@ -156,7 +156,9 @@ describe('ItemRelationshipsComponent', () => {
getRelatedItemsByLabel: observableOf([author1, author2]), getRelatedItemsByLabel: observableOf([author1, author2]),
getItemRelationshipsArray: observableOf(relationships), getItemRelationshipsArray: observableOf(relationships),
deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')), deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')),
getItemResolvedRelatedItemsAndRelationships: observableCombineLatest(observableOf([author1, author2]), observableOf([item, item]), observableOf(relationships)) getItemResolvedRelatedItemsAndRelationships: observableCombineLatest(observableOf([author1, author2]), observableOf([item, item]), observableOf(relationships)),
getRelationshipsByRelatedItemIds: observableOf(relationships),
getRelationshipTypeLabelsByItem: observableOf([relationshipType.leftwardType])
} }
); );

View File

@@ -2,8 +2,8 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs'; import { zip as observableZip } from 'rxjs';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
@@ -21,7 +21,6 @@ import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../core/shared/operators';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils';
@Component({ @Component({
selector: 'ds-item-relationships', selector: 'ds-item-relationships',
@@ -65,7 +64,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
*/ */
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item); this.relationLabels$ = this.relationshipService.getRelationshipTypeLabelsByItem(this.item);
this.initializeItemUpdate(); this.initializeItemUpdate();
} }
@@ -113,8 +112,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
); );
// Get all the relationships that should be removed // Get all the relationships that should be removed
const removedRelationships$ = removedItemIds$.pipe( const removedRelationships$ = removedItemIds$.pipe(
getRelationsByRelatedItemIds(this.item, this.relationshipService) flatMap((uuids) => this.relationshipService.getRelationshipsByRelatedItemIds(this.item, uuids))
); );
// const removedRelationships$ = removedItemIds$.pipe(flatMap((uuids: string[]) => this.relationshipService.getRelationshipsByRelatedItemIds(this.item, uuids)));
// Request a delete for every relationship found in the observable created above // Request a delete for every relationship found in the observable created above
removedRelationships$.pipe( removedRelationships$.pipe(
take(1), take(1),

View File

@@ -4,7 +4,6 @@ import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loa
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component'; import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe'; import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { ItemDataService } from '../../../../core/data/item-data.service'; import { ItemDataService } from '../../../../core/data/item-data.service';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service'; import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
@@ -15,6 +14,7 @@ import { createRelationshipsObservable } from '../shared/item.component.spec';
import { PublicationComponent } from './publication.component'; import { PublicationComponent } from './publication.component';
import { MetadataMap } from '../../../../core/shared/metadata.models'; import { MetadataMap } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { RelationshipService } from '../../../../core/data/relationship.service';
const mockItem: Item = Object.assign(new Item(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
@@ -26,12 +26,6 @@ describe('PublicationComponent', () => {
let comp: PublicationComponent; let comp: PublicationComponent;
let fixture: ComponentFixture<PublicationComponent>; let fixture: ComponentFixture<PublicationComponent>;
const searchFixedFilterServiceStub = {
/* tslint:disable:no-empty */
getQueryByRelations: () => {}
/* tslint:enable:no-empty */
};
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
@@ -43,8 +37,8 @@ describe('PublicationComponent', () => {
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe], declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
providers: [ providers: [
{provide: ItemDataService, useValue: {}}, {provide: ItemDataService, useValue: {}},
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub}, {provide: TruncatableService, useValue: {}},
{provide: TruncatableService, useValue: {}} {provide: RelationshipService, useValue: {}}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ItemComponent } from '../shared/item.component';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { ItemComponent } from '../shared/item.component';
import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
/** /**
* Component that represents a publication Item page * Component that represents a publication Item page

View File

@@ -1,14 +1,12 @@
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../../core/shared/operators';
import { hasNoValue, hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { distinctUntilChanged, flatMap, map, switchMap } from 'rxjs/operators'; import { distinctUntilChanged, flatMap, map, switchMap } from 'rxjs/operators';
import { zip as observableZip, combineLatest as observableCombineLatest } from 'rxjs'; import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { RelationshipService } from '../../../../core/data/relationship.service';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
/** /**
* Operator for comparing arrays using a mapping function * Operator for comparing arrays using a mapping function
@@ -37,36 +35,6 @@ export const compareArraysUsing = <T>(mapFn: (t: T) => any) =>
export const compareArraysUsingIds = <T extends { id: string }>() => export const compareArraysUsingIds = <T extends { id: string }>() =>
compareArraysUsing((t: T) => hasValue(t) ? t.id : undefined); compareArraysUsing((t: T) => hasValue(t) ? t.id : undefined);
/**
* Fetch the relationships which match the type label given
* @param {string} label Type label
* @param thisId The item's id of which the relations belong to
* @returns {(source: Observable<[Relationship[] , RelationshipType[]]>) => Observable<Relationship[]>}
*/
export const filterRelationsByTypeLabel = (label: string, thisId?: string) =>
(source: Observable<[Relationship[], RelationshipType[]]>): Observable<Relationship[]> =>
source.pipe(
switchMap(([relsCurrentPage, relTypesCurrentPage]) => {
const relatedItems$ = observableZip(...relsCurrentPage.map((rel: Relationship) =>
observableCombineLatest(
rel.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
rel.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()))
)
);
return relatedItems$.pipe(
map((arr) => relsCurrentPage.filter((rel: Relationship, idx: number) =>
hasValue(relTypesCurrentPage[idx]) && (
(hasNoValue(thisId) && (relTypesCurrentPage[idx].leftwardType === label ||
relTypesCurrentPage[idx].rightwardType === label)) ||
(thisId === arr[idx][0].id && relTypesCurrentPage[idx].leftwardType === label) ||
(thisId === arr[idx][1].id && relTypesCurrentPage[idx].rightwardType === label)
)
))
);
}),
distinctUntilChanged(compareArraysUsingIds())
);
/** /**
* Operator for turning a list of relationships into a list of the relevant items * Operator for turning a list of relationships into a list of the relevant items
* @param {string} thisId The item's id of which the relations belong to * @param {string} thisId The item's id of which the relations belong to
@@ -128,17 +96,3 @@ export const paginatedRelationsToItems = (thisId: string) =>
) )
}) })
); );
/**
* Operator for fetching an item's relationships, but filtered by related item IDs (essentially performing a reverse lookup)
* Only relationships where leftItem or rightItem's ID is present in the list provided will be returned
* @param item
* @param relationshipService
*/
export const getRelationsByRelatedItemIds = (item: Item, relationshipService: RelationshipService) =>
(source: Observable<string[]>): Observable<Relationship[]> =>
source.pipe(
flatMap((relatedItemIds: string[]) => relationshipService.getItemResolvedRelatedItemsAndRelationships(item).pipe(
map(([leftItems, rightItems, rels]) => rels.filter((rel: Relationship, index: number) => relatedItemIds.indexOf(leftItems[index].uuid) > -1 || relatedItemIds.indexOf(rightItems[index].uuid) > -1))
))
);

View File

@@ -9,7 +9,6 @@ import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loa
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe'; import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { isNotEmpty } from '../../../../shared/empty.util'; import { isNotEmpty } from '../../../../shared/empty.util';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
@@ -24,6 +23,7 @@ import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-rep
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models'; import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils'; import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils'; import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { RelationshipService } from '../../../../core/data/relationship.service';
/** /**
* Create a generic test for an item-page-fields component using a mockItem and the type of component * Create a generic test for an item-page-fields component using a mockItem and the type of component
@@ -37,12 +37,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
let comp: any; let comp: any;
let fixture: ComponentFixture<any>; let fixture: ComponentFixture<any>;
const searchFixedFilterServiceStub = {
/* tslint:disable:no-empty */
getQueryByRelations: () => {}
/* tslint:enable:no-empty */
};
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
@@ -54,8 +48,8 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
declarations: [component, GenericItemPageFieldComponent, TruncatePipe], declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
providers: [ providers: [
{provide: ItemDataService, useValue: {}}, {provide: ItemDataService, useValue: {}},
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub}, {provide: TruncatableService, useValue: {}},
{provide: TruncatableService, useValue: {}} {provide: RelationshipService, useValue: {}}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -1,4 +1,4 @@
import { Component, Inject, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
@Component({ @Component({

View File

@@ -1,17 +1,15 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Input } from '@angular/core';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model'; import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
import { Observable } from 'rxjs/internal/Observable'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { RelationshipService } from '../../../core/data/relationship.service'; import { RelationshipService } from '../../../core/data/relationship.service';
import { Item } from '../../../core/shared/item.model';
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip } from 'rxjs';
import { MetadataValue } from '../../../core/shared/metadata.models'; import { MetadataValue } from '../../../core/shared/metadata.models';
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { filter, map, switchMap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../core/shared/operators';
import { filter, map, switchMap } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
import { Item } from '../../../core/shared/item.model';
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model'; import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { Subscription } from 'rxjs/internal/Subscription';
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component'; import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
@Component({ @Component({

View File

@@ -4,13 +4,11 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
describe('RelatedEntitiesSearchComponent', () => { describe('RelatedEntitiesSearchComponent', () => {
let comp: RelatedEntitiesSearchComponent; let comp: RelatedEntitiesSearchComponent;
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>; let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
let fixedFilterService: SearchFixedFilterService;
const mockItem = Object.assign(new Item(), { const mockItem = Object.assign(new Item(), {
id: 'id1' id: 'id1'
@@ -18,17 +16,11 @@ describe('RelatedEntitiesSearchComponent', () => {
const mockRelationType = 'publicationsOfAuthor'; const mockRelationType = 'publicationsOfAuthor';
const mockConfiguration = 'publication'; const mockConfiguration = 'publication';
const mockFilter= `f.${mockRelationType}=${mockItem.id}`; const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
const fixedFilterServiceStub = {
getFilterByRelation: () => mockFilter
};
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
declarations: [RelatedEntitiesSearchComponent], declarations: [RelatedEntitiesSearchComponent],
providers: [
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents(); }).compileComponents();
})); }));
@@ -36,7 +28,6 @@ describe('RelatedEntitiesSearchComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent); fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
fixedFilterService = (comp as any).fixedFilterService;
comp.relationType = mockRelationType; comp.relationType = mockRelationType;
comp.item = mockItem; comp.item = mockItem;
comp.configuration = mockConfiguration; comp.configuration = mockConfiguration;

View File

@@ -1,9 +1,9 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { isNotEmpty } from '../../../../shared/empty.util'; import { isNotEmpty } from '../../../../shared/empty.util';
import { of } from 'rxjs/internal/observable/of'; import { of } from 'rxjs/internal/observable/of';
import { getFilterByRelation } from '../../../../shared/utils/relation-query.utils';
@Component({ @Component({
selector: 'ds-related-entities-search', selector: 'ds-related-entities-search',
@@ -47,12 +47,9 @@ export class RelatedEntitiesSearchComponent implements OnInit {
fixedFilter: string; fixedFilter: string;
configuration$: Observable<string>; configuration$: Observable<string>;
constructor(private fixedFilterService: SearchFixedFilterService) {
}
ngOnInit(): void { ngOnInit(): void {
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) { if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id); this.fixedFilter = getFilterByRelation(this.relationType, this.item.id);
} }
if (isNotEmpty(this.configuration)) { if (isNotEmpty(this.configuration)) {
this.configuration$ = of(this.configuration); this.configuration$ = of(this.configuration);

View File

@@ -3,9 +3,9 @@ import { Item } from '../../../core/shared/item.model';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { RelationshipService } from '../../../core/data/relationship.service';
import { FindListOptions } from '../../../core/data/request.models'; import { FindListOptions } from '../../../core/data/request.models';
import { ViewMode } from '../../../core/shared/view-mode.model'; import { ViewMode } from '../../../core/shared/view-mode.model';
import { RelationshipService } from '../../../core/data/relationship.service';
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component'; import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
@Component({ @Component({

View File

@@ -1,10 +1,10 @@
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service'; import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { SearchFilter } from '../+search-page/search-filter.model'; import { SearchFilter } from '../shared/search/search-filter.model';
import { ActivatedRouteStub } from '../shared/testing/active-router-stub'; import { ActivatedRouteStub } from '../shared/testing/active-router-stub';
import { MockRoleService } from '../shared/mocks/mock-role-service'; import { MockRoleService } from '../shared/mocks/mock-role-service';
import { cold, hot } from 'jasmine-marbles'; import { cold, hot } from 'jasmine-marbles';
@@ -38,12 +38,8 @@ describe('MyDSpaceConfigurationService', () => {
const roleService: any = new MockRoleService(); const roleService: any = new MockRoleService();
const fixedFilterService = jasmine.createSpyObj('SearchFixedFilterService', {
getQueryByFilterName: observableOf(''),
});
beforeEach(() => { beforeEach(() => {
service = new MyDSpaceConfigurationService(roleService, fixedFilterService, spy, activatedRoute); service = new MyDSpaceConfigurationService(roleService, spy, activatedRoute);
}); });
describe('when the scope is called', () => { describe('when the scope is called', () => {

View File

@@ -6,12 +6,11 @@ import { first, map } from 'rxjs/operators';
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type'; import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
import { RoleService } from '../core/roles/role.service'; import { RoleService } from '../core/roles/role.service';
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model'; import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { RouteService } from '../core/services/route.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service'; import { RouteService } from '../core/services/route.service';
/** /**
* Service that performs all actions that have to do with the current mydspace configuration * Service that performs all actions that have to do with the current mydspace configuration
@@ -55,16 +54,14 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService {
* Initialize class * Initialize class
* *
* @param {roleService} roleService * @param {roleService} roleService
* @param {SearchFixedFilterService} fixedFilterService
* @param {RouteService} routeService * @param {RouteService} routeService
* @param {ActivatedRoute} route * @param {ActivatedRoute} route
*/ */
constructor(protected roleService: RoleService, constructor(protected roleService: RoleService,
protected fixedFilterService: SearchFixedFilterService,
protected routeService: RouteService, protected routeService: RouteService,
protected route: ActivatedRoute) { protected route: ActivatedRoute) {
super(routeService, fixedFilterService, route); super(routeService, route);
// override parent class initialization // override parent class initialization
this._defaults = null; this._defaults = null;

View File

@@ -14,7 +14,7 @@ import { UploaderOptions } from '../../shared/uploader/uploader-options.model';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { NotificationType } from '../../shared/notifications/models/notification-type'; import { NotificationType } from '../../shared/notifications/models/notification-type';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { SearchResult } from '../../+search-page/search-result.model'; import { SearchResult } from '../../shared/search/search-result.model';
/** /**
* This component represents the whole mydspace page header * This component represents the whole mydspace page header

View File

@@ -19,15 +19,14 @@ import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.c
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { routeServiceStub } from '../shared/testing/route-service-stub'; import { routeServiceStub } from '../shared/testing/route-service-stub';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { SearchService } from '../+search-page/search-service/search.service'; import { SearchService } from '../core/shared/search/search.service';
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchFilterService } from '../+search-page/search-filters/search-filter/search-filter.service'; import { SearchFilterService } from '../core/shared/search/search-filter.service';
import { RoleDirective } from '../shared/roles/role.directive'; import { RoleDirective } from '../shared/roles/role.directive';
import { RoleService } from '../core/roles/role.service'; import { RoleService } from '../core/roles/role.service';
import { MockRoleService } from '../shared/mocks/mock-role-service'; import { MockRoleService } from '../shared/mocks/mock-role-service';
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils'; import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
describe('MyDSpacePageComponent', () => { describe('MyDSpacePageComponent', () => {
@@ -82,8 +81,6 @@ describe('MyDSpacePageComponent', () => {
collapse: () => this.isCollapsed = observableOf(true), collapse: () => this.isCollapsed = observableOf(true),
expand: () => this.isCollapsed = observableOf(false) expand: () => this.isCollapsed = observableOf(false)
}; };
const mockFixedFilterService: SearchFixedFilterService = {
} as SearchFixedFilterService;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -123,10 +120,6 @@ describe('MyDSpacePageComponent', () => {
provide: RoleService, provide: RoleService,
useValue: new MockRoleService() useValue: new MockRoleService()
}, },
{
provide: SearchFixedFilterService,
useValue: mockFixedFilterService
}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(MyDSpacePageComponent, { }).overrideComponent(MyDSpacePageComponent, {

View File

@@ -15,19 +15,19 @@ import { RemoteData } from '../core/data/remote-data';
import { DSpaceObject } from '../core/shared/dspace-object.model'; import { DSpaceObject } from '../core/shared/dspace-object.model';
import { pushInOut } from '../shared/animations/push'; import { pushInOut } from '../shared/animations/push';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchService } from '../+search-page/search-service/search.service'; import { SearchService } from '../core/shared/search/search.service';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getSucceededRemoteData } from '../core/shared/operators';
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service'; import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model'; import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
import { RoleType } from '../core/roles/role-types'; import { RoleType } from '../core/roles/role-types';
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service'; import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
import { ViewMode } from '../core/shared/view-mode.model'; import { ViewMode } from '../core/shared/view-mode.model';
import { MyDSpaceRequest } from '../core/data/request.models'; import { MyDSpaceRequest } from '../core/data/request.models';
import { SearchResult } from '../+search-page/search-result.model'; import { SearchResult } from '../shared/search/search-result.model';
import { Context } from '../core/shared/context.model'; import { Context } from '../core/shared/context.model';
export const MYDSPACE_ROUTE = '/mydspace'; export const MYDSPACE_ROUTE = '/mydspace';

View File

@@ -5,7 +5,6 @@ import { SharedModule } from '../shared/shared.module';
import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module'; import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module';
import { MyDSpacePageComponent } from './my-dspace-page.component'; import { MyDSpacePageComponent } from './my-dspace-page.component';
import { SearchPageModule } from '../+search-page/search-page.module';
import { MyDSpaceResultsComponent } from './my-dspace-results/my-dspace-results.component'; import { MyDSpaceResultsComponent } from './my-dspace-results/my-dspace-results.component';
import { WorkspaceItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component'; import { WorkspaceItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component';
import { ClaimedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component'; import { ClaimedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component';
@@ -27,7 +26,6 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/
CommonModule, CommonModule,
SharedModule, SharedModule,
MyDspacePageRoutingModule, MyDspacePageRoutingModule,
SearchPageModule
], ],
declarations: [ declarations: [
MyDSpacePageComponent, MyDSpacePageComponent,

View File

@@ -2,12 +2,12 @@ import { Component, Input } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { fadeIn, fadeInOut } from '../../shared/animations/fade';
import { SearchOptions } from '../../+search-page/search-options.model'; import { SearchOptions } from '../../shared/search/search-options.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
import { ViewMode } from '../../core/shared/view-mode.model'; import { ViewMode } from '../../core/shared/view-mode.model';
import { isEmpty } from '../../shared/empty.util'; import { isEmpty } from '../../shared/empty.util';
import { SearchResult } from '../../+search-page/search-result.model';
import { Context } from '../../core/shared/context.model'; import { Context } from '../../core/shared/context.model';
import { SearchResult } from '../../shared/search/search-result.model';
/** /**
* Component that represents all results for mydspace page * Component that represents all results for mydspace page

View File

@@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { configureSearchComponentTestingModule } from './search.component.spec'; import { configureSearchComponentTestingModule } from './search.component.spec';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
describe('ConfigurationSearchPageComponent', () => { describe('ConfigurationSearchPageComponent', () => {
let comp: ConfigurationSearchPageComponent; let comp: ConfigurationSearchPageComponent;

View File

@@ -1,15 +1,14 @@
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { SearchService } from './search-service/search.service';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchComponent } from './search.component'; import { SearchComponent } from './search.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push'; import { pushInOut } from '../shared/animations/push';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { Observable } from 'rxjs';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { map } from 'rxjs/operators'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { Router } from '@angular/router';
import { hasValue } from '../shared/empty.util';
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { SearchService } from '../core/shared/search/search.service';
/** /**
* This component renders a search page using a configuration as input. * This component renders a search page using a configuration as input.
@@ -45,8 +44,9 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements
protected sidebarService: SidebarService, protected sidebarService: SidebarService,
protected windowService: HostWindowService, protected windowService: HostWindowService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
protected routeService: RouteService) { protected routeService: RouteService,
super(service, sidebarService, windowService, searchConfigService, routeService); protected router: Router) {
super(service, sidebarService, windowService, searchConfigService, routeService, router);
} }
/** /**
@@ -58,24 +58,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent implements
*/ */
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
if (hasValue(this.configuration)) {
this.routeService.setParameter('configuration', this.configuration);
} }
/**
* Get the current paginated search options after updating the configuration using the configuration input
* This is to make sure the configuration is included in the paginated search options, as it is not part of any
* query or route parameters
* @returns {Observable<PaginatedSearchOptions>}
*/
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
return this.searchConfigService.paginatedSearchOptions.pipe(
map((options: PaginatedSearchOptions) => {
const config = this.configuration || options.configuration;
const filter = this.fixedFilterQuery || options.fixedFilter;
return Object.assign(options, {
configuration: config,
fixedFilter: filter
});
})
);
} }
} }

View File

@@ -1,38 +0,0 @@
import { SearchFixedFilterService } from './search-fixed-filter.service';
import { RequestService } from '../../../core/data/request.service';
import { of as observableOf } from 'rxjs';
import { RequestEntry } from '../../../core/data/request.reducer';
import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models';
describe('SearchFixedFilterService', () => {
let service: SearchFixedFilterService;
const filterQuery = 'filter:query';
const requestServiceStub = Object.assign({
/* tslint:disable:no-empty */
configure: () => {
},
/* tslint:enable:no-empty */
generateRequestId: () => 'fake-id',
getByHref: () => observableOf(Object.assign(new RequestEntry(), {
response: new FilteredDiscoveryQueryResponse(filterQuery, 200, 'OK')
}))
}) as RequestService;
beforeEach(() => {
service = new SearchFixedFilterService();
});
describe('when getQueryByRelations is called', () => {
const relationType = 'isRelationOf';
const itemUUID = 'c5b277e6-2477-48bb-8993-356710c285f3';
it('should contain the relationType and itemUUID', () => {
const query = service.getQueryByRelations(relationType, itemUUID);
expect(query.length).toBeGreaterThan(relationType.length + itemUUID.length);
expect(query).toContain(relationType);
expect(query).toContain(itemUUID);
});
});
});

View File

@@ -1,27 +0,0 @@
import { Injectable } from '@angular/core';
/**
* Service for performing actions on the filtered-discovery-pages REST endpoint
*/
@Injectable()
export class SearchFixedFilterService {
/**
* Get the query for looking up items by relation type
* @param {string} relationType Relation type
* @param {string} itemUUID Item UUID
* @returns {string} Query
*/
getQueryByRelations(relationType: string, itemUUID: string): string {
return `query=relation.${relationType}:${itemUUID}`;
}
/**
* Get the filter for a relation with the item's UUID
* @param relationType The type of relation e.g. 'isAuthorOfPublication'
* @param itemUUID The item's UUID
*/
getFilterByRelation(relationType: string, itemUUID: string): string {
return `f.${relationType}=${itemUUID}`;
}
}

View File

@@ -1,7 +0,0 @@
<div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3">
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)">
<ds-search-label *ngFor="let value of (appliedFilters | async)[key]" [inPlaceSearch]="inPlaceSearch" [key]="key" [value]="value" [appliedFilters]="appliedFilters"></ds-search-label>
</ng-container>
</div>
</div>

View File

@@ -1,7 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { SearchComponent } from './search.component';
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
import { SearchPageComponent } from './search-page.component'; import { SearchPageComponent } from './search-page.component';

View File

@@ -3,68 +3,18 @@ import { CommonModule } from '@angular/common';
import { CoreModule } from '../core/core.module'; import { CoreModule } from '../core/core.module';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { SearchPageRoutingModule } from './search-page-routing.module'; import { SearchPageRoutingModule } from './search-page-routing.module';
import { SearchComponent } from './search.component'; import { SearchPageComponent } from './search-page.component';
import { SearchResultsComponent } from './search-results/search-results.component';
import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'
import { CollectionSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component';
import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component';
import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SidebarEffects } from '../shared/sidebar/sidebar-effects.service';
import { SearchSettingsComponent } from './search-settings/search-settings.component';
import { EffectsModule } from '@ngrx/effects';
import { SearchFiltersComponent } from './search-filters/search-filters.component';
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
import { SearchLabelsComponent } from './search-labels/search-labels.component';
import { SearchRangeFilterComponent } from './search-filters/search-filter/search-range-filter/search-range-filter.component';
import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { SearchFacetOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component';
import { SearchFacetSelectedOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component';
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
import { SearchLabelComponent } from './search-labels/search-label/search-label.component';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
import { SearchPageComponent } from './search-page.component';
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
import { StatisticsModule } from '../statistics/statistics.module';
import { SearchTrackerComponent } from './search-tracker.component'; import { SearchTrackerComponent } from './search-tracker.component';
import { StatisticsModule } from '../statistics/statistics.module';
const effects = [ import { SearchComponent } from './search.component';
SidebarEffects
];
const components = [ const components = [
SearchPageComponent, SearchPageComponent,
SearchComponent, SearchComponent,
SearchResultsComponent,
SearchSidebarComponent,
SearchSettingsComponent,
SearchFiltersComponent,
SearchFilterComponent,
SearchFacetFilterComponent,
SearchLabelsComponent,
SearchLabelComponent,
SearchFacetFilterComponent,
SearchFacetFilterWrapperComponent,
SearchRangeFilterComponent,
SearchTextFilterComponent,
SearchHierarchyFilterComponent,
SearchBooleanFilterComponent,
SearchFacetOptionComponent,
SearchFacetSelectedOptionComponent,
SearchFacetRangeOptionComponent,
SearchSwitchConfigurationComponent,
SearchAuthorityFilterComponent,
ConfigurationSearchPageComponent, ConfigurationSearchPageComponent,
SearchTrackerComponent, SearchTrackerComponent
]; ];
@NgModule({ @NgModule({
@@ -72,30 +22,11 @@ const components = [
SearchPageRoutingModule, SearchPageRoutingModule,
CommonModule, CommonModule,
SharedModule, SharedModule,
EffectsModule.forFeature(effects),
CoreModule.forRoot(), CoreModule.forRoot(),
StatisticsModule.forRoot(), StatisticsModule.forRoot(),
], ],
providers: [ConfigurationSearchPageGuard],
declarations: components, declarations: components,
providers: [
SidebarService,
SidebarFilterService,
SearchFilterService,
SearchFixedFilterService,
ConfigurationSearchPageGuard,
SearchConfigurationService
],
entryComponents: [
SearchFacetFilterComponent,
SearchRangeFilterComponent,
SearchTextFilterComponent,
SearchHierarchyFilterComponent,
SearchBooleanFilterComponent,
SearchFacetOptionComponent,
SearchFacetSelectedOptionComponent,
SearchFacetRangeOptionComponent,
SearchAuthorityFilterComponent
],
exports: components exports: components
}) })

View File

@@ -1,26 +0,0 @@
import { DSpaceObject } from '../core/shared/dspace-object.model';
import { MetadataMap } from '../core/shared/metadata.models';
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
import { GenericConstructor } from '../core/shared/generic-constructor';
/**
* Represents a search result object of a certain (<T>) DSpaceObject
*/
export class SearchResult<T extends DSpaceObject> implements ListableObject {
/**
* The DSpaceObject that was found
*/
indexableObject: T;
/**
* The metadata that was used to find this item, hithighlighted
*/
hitHighlights: MetadataMap;
/**
* Method that returns as which type of object this object should be rendered
*/
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
return [this.constructor as GenericConstructor<ListableObject>];
}
}

View File

@@ -1,19 +0,0 @@
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
<ds-viewable-collection
[config]="searchConfig.pagination"
[sortConfig]="searchConfig.sort"
[objects]="searchResults"
[linkType]="linkType"
[hideGear]="true">
</ds-viewable-collection></div>
<ds-loading *ngIf="hasNoValue(searchResults) || hasNoValue(searchResults.payload) || searchResults.isLoading" message="{{'loading.search-results' | translate}}"></ds-loading>
<ds-error *ngIf="searchResults?.hasFailed && (!searchResults?.error || searchResults?.error?.statusCode != 400)" message="{{'error.search-results' | translate}}"></ds-error>
<div *ngIf="searchResults?.payload?.page.length == 0 || searchResults?.error?.statusCode == 400">
{{ 'search.results.no-results' | translate }}
<a [routerLink]="['/search']"
[queryParams]="{ query: surroundStringWithQuotes(searchConfig?.query) }"
queryParamsHandling="merge">
{{"search.results.no-results-link" | translate}}
</a>
</div>

View File

@@ -1,32 +0,0 @@
<ng-container *ngVar="(searchOptions$ | async) as config">
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
<div class="result-order-settings">
<ds-sidebar-dropdown
*ngIf="config?.sort"
[id]="'search-sidebar-sort'"
[label]="'search.sidebar.settings.sort-by'"
(change)="reloadOrder($event)"
>
<option *ngFor="let sortOption of searchOptionPossibilities"
[value]="sortOption.field + ',' + sortOption.direction.toString()"
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
</option>
</ds-sidebar-dropdown>
</div>
<div class="page-size-settings">
<ds-sidebar-dropdown
[id]="'search-sidebar-rpp'"
[label]="'search.sidebar.settings.rpp'"
(change)="reloadRPP($event)"
>
<option *ngFor="let pageSizeOption of config?.pagination.pageSizeOptions"
[value]="pageSizeOption"
[selected]="pageSizeOption === +config?.pagination.pageSize ? 'selected': null">
{{pageSizeOption}}
</option>
</ds-sidebar-dropdown>
</div>
</ng-container>

View File

@@ -2,16 +2,17 @@ import { Component, Inject, OnInit } from '@angular/core';
import { Angulartics2 } from 'angulartics2'; import { Angulartics2 } from 'angulartics2';
import { filter, map, switchMap } from 'rxjs/operators'; import { filter, map, switchMap } from 'rxjs/operators';
import { SearchComponent } from './search.component'; import { SearchComponent } from './search.component';
import { SearchService } from './search-service/search.service';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { SearchQueryResponse } from './search-service/search-query-response.model';
import { SearchSuccessResponse } from '../core/cache/response.models'; import { SearchSuccessResponse } from '../core/cache/response.models';
import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { Router } from '@angular/router';
import { SearchService } from '../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchQueryResponse } from '../shared/search/search-query-response.model';
/** /**
* This component triggers a page view statistic * This component triggers a page view statistic
@@ -30,14 +31,15 @@ import { PaginatedSearchOptions } from './paginated-search-options.model';
export class SearchTrackerComponent extends SearchComponent implements OnInit { export class SearchTrackerComponent extends SearchComponent implements OnInit {
constructor( constructor(
protected service:SearchService, protected service: SearchService,
protected sidebarService:SidebarService, protected sidebarService: SidebarService,
protected windowService:HostWindowService, protected windowService: HostWindowService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService:SearchConfigurationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
protected routeService:RouteService, protected routeService: RouteService,
public angulartics2:Angulartics2 public angulartics2: Angulartics2,
protected router: Router
) { ) {
super(service, sidebarService, windowService, searchConfigService, routeService); super(service, sidebarService, windowService, searchConfigService, routeService, router);
} }
ngOnInit():void { ngOnInit():void {
@@ -58,9 +60,9 @@ export class SearchTrackerComponent extends SearchComponent implements OnInit {
) )
) )
.subscribe((entry) => { .subscribe((entry) => {
const config:PaginatedSearchOptions = entry.searchOptions; const config: PaginatedSearchOptions = entry.searchOptions;
const searchQueryResponse:SearchQueryResponse = entry.response; const searchQueryResponse: SearchQueryResponse = entry.response;
const filters:Array<{ filter:string, operator:string, value:string, label:string; }> = []; const filters:Array<{ filter: string, operator: string, value: string, label: string; }> = [];
const appliedFilters = searchQueryResponse.appliedFilters || []; const appliedFilters = searchQueryResponse.appliedFilters || [];
for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) { for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) {
const appliedFilter = appliedFilters[i]; const appliedFilter = appliedFilters[i];

View File

@@ -46,5 +46,9 @@
[scopes]="(scopeListRD$ | async)" [scopes]="(scopeListRD$ | async)"
[inPlaceSearch]="inPlaceSearch"> [inPlaceSearch]="inPlaceSearch">
</ds-search-form> </ds-search-form>
<div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3">
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels> <ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
</div>
</div>
</ng-template> </ng-template>

View File

@@ -11,21 +11,19 @@ import { CommunityDataService } from '../core/data/community-data.service';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { SearchComponent } from './search.component'; import { SearchComponent } from './search.component';
import { SearchService } from './search-service/search.service'; import { SearchService } from '../core/shared/search/search.service';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchFilterService } from './search-filters/search-filter/search-filter.service'; import { SearchFilterService } from '../core/shared/search/search-filter.service';
import { SearchConfigurationService } from './search-service/search-configuration.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { RemoteData } from '../core/data/remote-data';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils'; import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
let comp: SearchComponent; let comp: SearchComponent;
let fixture: ComponentFixture<SearchComponent>; let fixture: ComponentFixture<SearchComponent>;
@@ -89,7 +87,6 @@ const routeServiceStub = {
return observableOf('') return observableOf('')
} }
}; };
const mockFixedFilterService: SearchFixedFilterService = {} as SearchFixedFilterService;
export function configureSearchComponentTestingModule(compType) { export function configureSearchComponentTestingModule(compType) {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -122,10 +119,6 @@ export function configureSearchComponentTestingModule(compType) {
provide: SearchFilterService, provide: SearchFilterService,
useValue: {} useValue: {}
}, },
{
provide: SearchFixedFilterService,
useValue: mockFixedFilterService
},
{ {
provide: SearchConfigurationService, provide: SearchConfigurationService,
useValue: { useValue: {
@@ -158,6 +151,7 @@ describe('SearchComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent); fixture = TestBed.createComponent(SearchComponent);
comp = fixture.componentInstance; // SearchComponent test instance comp = fixture.componentInstance; // SearchComponent test instance
comp.inPlaceSearch = false;
fixture.detectChanges(); fixture.detectChanges();
searchServiceObject = (comp as any).service; searchServiceObject = (comp as any).service;
searchConfigurationServiceObject = (comp as any).searchConfigService; searchConfigurationServiceObject = (comp as any).searchConfigService;

View File

@@ -1,20 +1,22 @@
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { startWith, switchMap, } from 'rxjs/operators'; import { startWith, switchMap, } from 'rxjs/operators';
import { PaginatedList } from '../core/data/paginated-list'; import { PaginatedList } from '../core/data/paginated-list';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { DSpaceObject } from '../core/shared/dspace-object.model'; import { DSpaceObject } from '../core/shared/dspace-object.model';
import { pushInOut } from '../shared/animations/push'; import { pushInOut } from '../shared/animations/push';
import { HostWindowService } from '../shared/host-window.service'; import { HostWindowService } from '../shared/host-window.service';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SearchResult } from './search-result.model';
import { SearchService } from './search-service/search.service';
import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SidebarService } from '../shared/sidebar/sidebar.service';
import { hasValue, isNotEmpty } from '../shared/empty.util'; import { hasValue, isNotEmpty } from '../shared/empty.util';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getSucceededRemoteData } from '../core/shared/operators';
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { SearchResult } from '../shared/search/search-result.model';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchService } from '../core/shared/search/search.service';
import { currentPath } from '../shared/utils/route.utils';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'ds-search', selector: 'ds-search',
@@ -96,7 +98,8 @@ export class SearchComponent implements OnInit {
protected sidebarService: SidebarService, protected sidebarService: SidebarService,
protected windowService: HostWindowService, protected windowService: HostWindowService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
protected routeService: RouteService) { protected routeService: RouteService,
protected router: Router) {
this.isXsOrSm$ = this.windowService.isXsOrSm(); this.isXsOrSm$ = this.windowService.isXsOrSm();
} }
@@ -159,7 +162,7 @@ export class SearchComponent implements OnInit {
*/ */
private getSearchLink(): string { private getSearchLink(): string {
if (this.inPlaceSearch) { if (this.inPlaceSearch) {
return './'; return currentPath(this.router);
} }
return this.service.getSearchLink(); return this.service.getSearchLink();
} }

View File

@@ -20,7 +20,7 @@ import { Store, StoreModule } from '@ngrx/store';
// Load the implementations that should be tested // Load the implementations that should be tested
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowState } from './shared/search/host-window.reducer';
import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowResizeAction } from './shared/host-window.actions';
import { MetadataService } from './core/metadata/metadata.service'; import { MetadataService } from './core/metadata/metadata.service';

View File

@@ -1,13 +1,5 @@
import { filter, map, take } from 'rxjs/operators'; import { filter, map, take } from 'rxjs/operators';
import { import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
AfterViewInit,
ChangeDetectionStrategy,
Component,
HostListener,
Inject,
OnInit,
ViewEncapsulation
} from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router'; import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { select, Store } from '@ngrx/store'; import { select, Store } from '@ngrx/store';
@@ -18,12 +10,11 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { MetadataService } from './core/metadata/metadata.service'; import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowState } from './shared/search/host-window.reducer';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { isAuthenticated } from './core/auth/selectors'; import { isAuthenticated } from './core/auth/selectors';
import { AuthService } from './core/auth/auth.service'; import { AuthService } from './core/auth/auth.service';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { RouteService } from './core/services/route.service';
import variables from '../styles/_exposed_variables.scss'; import variables from '../styles/_exposed_variables.scss';
import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
import { MenuService } from './shared/menu/menu.service'; import { MenuService } from './shared/menu/menu.service';

View File

@@ -1,9 +1,13 @@
import { StoreEffects } from './store.effects'; import { StoreEffects } from './store.effects';
import { NotificationsEffects } from './shared/notifications/notifications.effects'; import { NotificationsEffects } from './shared/notifications/notifications.effects';
import { NavbarEffects } from './navbar/navbar.effects'; import { NavbarEffects } from './navbar/navbar.effects';
import { SidebarEffects } from './shared/sidebar/sidebar-effects.service';
import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
export const appEffects = [ export const appEffects = [
StoreEffects, StoreEffects,
NavbarEffects, NavbarEffects,
NotificationsEffects, NotificationsEffects,
SidebarEffects,
RelationshipEffects
]; ];

View File

@@ -37,9 +37,9 @@ import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.comp
import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
import { NavbarModule } from './navbar/navbar.module'; import { NavbarModule } from './navbar/navbar.module';
import { ClientCookieService } from './core/services/client-cookie.service';
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
import { ClientCookieService } from './core/services/client-cookie.service';
export function getConfig() { export function getConfig() {
return ENV_CONFIG; return ENV_CONFIG;
@@ -76,7 +76,7 @@ const ENTITY_IMPORTS = [
IMPORTS.push( IMPORTS.push(
StoreDevtoolsModule.instrument({ StoreDevtoolsModule.instrument({
maxAge: 100, maxAge: 1000,
logOnly: ENV_CONFIG.production, logOnly: ENV_CONFIG.production,
}) })
); );

View File

@@ -1,38 +1,22 @@
import { ActionReducerMap, createSelector, MemoizedSelector, State } from '@ngrx/store'; import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
import * as fromRouter from '@ngrx/router-store'; import * as fromRouter from '@ngrx/router-store';
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer'; import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
import { formReducer, FormState } from './shared/form/form.reducer'; import { formReducer, FormState } from './shared/form/form.reducer';
import { import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
SidebarState, import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
sidebarReducer import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
} from './shared/sidebar/sidebar.reducer'; import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
import {
SidebarFilterState,
sidebarFilterReducer, SidebarFiltersState
} from './shared/sidebar/filter/sidebar-filter.reducer';
import {
filterReducer,
SearchFiltersState
} from './+search-page/search-filters/search-filter/search-filter.reducer';
import {
notificationsReducer,
NotificationsState
} from './shared/notifications/notifications.reducers';
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
import { import { metadataRegistryReducer, MetadataRegistryState } from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
metadataRegistryReducer,
MetadataRegistryState
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
import { hasValue } from './shared/empty.util'; import { hasValue } from './shared/empty.util';
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer'; import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
import { menusReducer, MenusState } from './shared/menu/menu.reducer'; import { menusReducer, MenusState } from './shared/menu/menu.reducer';
import { historyReducer, HistoryState } from './shared/history/history.reducer'; import { historyReducer, HistoryState } from './shared/history/history.reducer';
import { import { selectableListReducer, SelectableListsState } from './shared/object-list/selectable-list/selectable-list.reducer';
bitstreamFormatReducer, import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
BitstreamFormatRegistryState
} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer'; import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
import { NameVariantListsState, nameVariantReducer } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
export interface AppState { export interface AppState {
router: fromRouter.RouterReducerState; router: fromRouter.RouterReducerState;
@@ -49,6 +33,8 @@ export interface AppState {
cssVariables: CSSVariablesState; cssVariables: CSSVariablesState;
menus: MenusState; menus: MenusState;
objectSelection: ObjectSelectionListState; objectSelection: ObjectSelectionListState;
selectableLists: SelectableListsState;
relationshipLists: NameVariantListsState;
communityList: CommunityListState; communityList: CommunityListState;
} }
@@ -67,6 +53,8 @@ export const appReducers: ActionReducerMap<AppState> = {
cssVariables: cssVariablesReducer, cssVariables: cssVariablesReducer,
menus: menusReducer, menus: menusReducer,
objectSelection: objectSelectionReducer, objectSelection: objectSelectionReducer,
selectableLists: selectableListReducer,
relationshipLists: nameVariantReducer,
communityList: CommunityListReducer, communityList: CommunityListReducer,
}; };

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { NormalizedObject } from '../models/normalized-object.model'; import { NormalizedObject } from '../models/normalized-object.model';
import { getMapsToType, getRelationships } from './build-decorators'; import { getMapsToType, getRelationships } from './build-decorators';
import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { TypedObject } from '../object-cache.reducer'; import { CacheableObject, TypedObject } from '../object-cache.reducer';
/** /**
* Return true if halObj has a value for `_links.self` * Return true if halObj has a value for `_links.self`
@@ -34,14 +34,13 @@ export class NormalizedObjectBuildService {
* *
* @param {TDomain} domainModel a domain model * @param {TDomain} domainModel a domain model
*/ */
normalize<T extends TypedObject>(domainModel: T): NormalizedObject<T> { normalize<T extends CacheableObject>(domainModel: T): NormalizedObject<T> {
const normalizedConstructor = getMapsToType((domainModel as any).type); const normalizedConstructor = getMapsToType((domainModel as any).type);
const relationships = getRelationships(normalizedConstructor) || []; const relationships = getRelationships(normalizedConstructor) || [];
const normalizedModel = Object.assign({}, domainModel) as any; const normalizedModel = Object.assign({}, domainModel) as any;
relationships.forEach((key: string) => { relationships.forEach((key: string) => {
if (hasValue(domainModel[key])) { if (hasValue(normalizedModel[key])) {
domainModel[key] = undefined; normalizedModel[key] = normalizedModel._links[key];
} }
}); });
return normalizedModel; return normalizedModel;

View File

@@ -116,7 +116,7 @@ export class RemoteDataBuildService {
const requestEntry$ = href$.pipe(getRequestFromRequestHref(this.requestService)); const requestEntry$ = href$.pipe(getRequestFromRequestHref(this.requestService));
const tDomainList$ = requestEntry$.pipe( const tDomainList$ = requestEntry$.pipe(
getResourceLinksFromResponse(), getResourceLinksFromResponse(),
flatMap((resourceUUIDs: string[]) => { switchMap((resourceUUIDs: string[]) => {
return this.objectCache.getList(resourceUUIDs).pipe( return this.objectCache.getList(resourceUUIDs).pipe(
map((normList: Array<NormalizedObject<T>>) => { map((normList: Array<NormalizedObject<T>>) => {
return normList.map((normalized: NormalizedObject<T>) => { return normList.map((normalized: NormalizedObject<T>) => {
@@ -273,12 +273,14 @@ export class RemoteDataBuildService {
private toPaginatedList<T>(input: Observable<RemoteData<T[] | PaginatedList<T>>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> { private toPaginatedList<T>(input: Observable<RemoteData<T[] | PaginatedList<T>>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
return input.pipe( return input.pipe(
map((rd: RemoteData<T[] | PaginatedList<T>>) => { map((rd: RemoteData<T[] | PaginatedList<T>>) => {
const rdAny = rd as any;
const newRD = new RemoteData(rdAny.requestPending, rdAny.responsePending, rdAny.isSuccessful, rd.error, undefined);
if (Array.isArray(rd.payload)) { if (Array.isArray(rd.payload)) {
return Object.assign(rd, { payload: new PaginatedList(pageInfo, rd.payload) }) return Object.assign(newRD, { payload: new PaginatedList(pageInfo, rd.payload) })
} else if (isNotUndefined(rd.payload)) { } else if (isNotUndefined(rd.payload)) {
return Object.assign(rd, { payload: new PaginatedList(pageInfo, rd.payload.page) }); return Object.assign(newRD, { payload: new PaginatedList(pageInfo, rd.payload.page) });
} else { } else {
return Object.assign(rd, { payload: new PaginatedList(pageInfo, []) }); return Object.assign(newRD, { payload: new PaginatedList(pageInfo, []) });
} }
}) })
); );

View File

@@ -1,4 +1,4 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; import { autoserialize, deserialize, deserializeAs, inheritSerialization } from 'cerialize';
import { Relationship } from '../../../shared/item-relationships/relationship.model'; import { Relationship } from '../../../shared/item-relationships/relationship.model';
import { mapsTo, relationship } from '../../builders/build-decorators'; import { mapsTo, relationship } from '../../builders/build-decorators';
import { NormalizedObject } from '../normalized-object.model'; import { NormalizedObject } from '../normalized-object.model';
@@ -16,20 +16,20 @@ export class NormalizedRelationship extends NormalizedObject<Relationship> {
/** /**
* The identifier of this Relationship * The identifier of this Relationship
*/ */
@autoserialize @deserialize
id: string; id: string;
/** /**
* The item to the left of this relationship * The item to the left of this relationship
*/ */
@autoserialize @deserialize
@relationship(Item, false) @relationship(Item, false)
leftItem: string; leftItem: string;
/** /**
* The item to the right of this relationship * The item to the right of this relationship
*/ */
@autoserialize @deserialize
@relationship(Item, false) @relationship(Item, false)
rightItem: string; rightItem: string;
@@ -46,15 +46,27 @@ export class NormalizedRelationship extends NormalizedObject<Relationship> {
rightPlace: number; rightPlace: number;
/** /**
* The type of Relationship * The name variant of the Item to the left side of this Relationship
*/ */
@autoserialize @autoserialize
leftwardValue: string;
/**
* The name variant of the Item to the right side of this Relationship
*/
@autoserialize
rightwardValue: string;
/**
* The type of Relationship
*/
@deserialize
@relationship(RelationshipType, false) @relationship(RelationshipType, false)
relationshipType: string; relationshipType: string;
/** /**
* The universally unique identifier of this Relationship * The universally unique identifier of this Relationship
*/ */
@autoserializeAs(new IDToUUIDSerializer(Relationship.type.value), 'id') @deserializeAs(new IDToUUIDSerializer(Relationship.type.value), 'id')
uuid: string; uuid: string;
} }

View File

@@ -1,5 +1,5 @@
import { CacheableObject, TypedObject } from '../object-cache.reducer'; import { CacheableObject, TypedObject } from '../object-cache.reducer';
import { autoserialize } from 'cerialize'; import { autoserialize, deserialize } from 'cerialize';
import { ResourceType } from '../../shared/resource-type'; import { ResourceType } from '../../shared/resource-type';
/** /**
* An abstract model class for a NormalizedObject. * An abstract model class for a NormalizedObject.
@@ -8,10 +8,10 @@ export abstract class NormalizedObject<T extends TypedObject> implements Cacheab
/** /**
* The link to the rest endpoint where this object can be found * The link to the rest endpoint where this object can be found
*/ */
@autoserialize @deserialize
self: string; self: string;
@autoserialize @deserialize
_links: { _links: {
[name: string]: string [name: string]: string
}; };
@@ -19,6 +19,6 @@ export abstract class NormalizedObject<T extends TypedObject> implements Cacheab
/** /**
* A string representing the kind of object * A string representing the kind of object
*/ */
@autoserialize @deserialize
type: string; type: string;
} }

View File

@@ -196,8 +196,9 @@ export class ObjectCacheService {
* false otherwise * false otherwise
*/ */
hasByUUID(uuid: string): boolean { hasByUUID(uuid: string): boolean {
let result: boolean; let result = false;
/* NB: that this is only a solution because the select method is synchronous, see: https://github.com/ngrx/store/issues/296#issuecomment-269032571*/
this.store.pipe( this.store.pipe(
select(selfLinkFromUuidSelector(uuid)), select(selfLinkFromUuidSelector(uuid)),
take(1) take(1)

View File

@@ -1,9 +1,9 @@
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model'; import { SearchQueryResponse } from '../../shared/search/search-query-response.model';
import { RequestError } from '../data/request.models'; import { RequestError } from '../data/request.models';
import { PageInfo } from '../shared/page-info.model'; import { PageInfo } from '../shared/page-info.model';
import { ConfigObject } from '../config/models/config.model'; import { ConfigObject } from '../config/models/config.model';
import { FacetValue } from '../../+search-page/search-service/facet-value.model'; import { FacetValue } from '../../shared/search/facet-value.model';
import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
import { IntegrationModel } from '../integration/models/integration.model'; import { IntegrationModel } from '../integration/models/integration.model';
import { RegistryMetadataschemasResponse } from '../registry/registry-metadataschemas-response.model'; import { RegistryMetadataschemasResponse } from '../registry/registry-metadataschemas-response.model';
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model'; import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';

View File

@@ -6,6 +6,7 @@ import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.e
import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects';
import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects'; import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects';
import { RouteEffects } from './services/route.effects'; import { RouteEffects } from './services/route.effects';
import { RouterEffects } from './router/router.effects';
export const coreEffects = [ export const coreEffects = [
RequestEffects, RequestEffects,
@@ -15,5 +16,6 @@ export const coreEffects = [
JsonPatchOperationsEffects, JsonPatchOperationsEffects,
ServerSyncBufferEffects, ServerSyncBufferEffects,
ObjectUpdatesEffects, ObjectUpdatesEffects,
RouteEffects RouteEffects,
RouterEffects
]; ];

View File

@@ -53,7 +53,7 @@ import { UUIDService } from './shared/uuid.service';
import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthenticatedGuard } from './auth/authenticated.guard';
import { AuthRequestService } from './auth/auth-request.service'; import { AuthRequestService } from './auth/auth-request.service';
import { AuthResponseParsingService } from './auth/auth-response-parsing.service'; import { AuthResponseParsingService } from './auth/auth-response-parsing.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { AuthInterceptor } from './auth/auth.interceptor'; import { AuthInterceptor } from './auth/auth.interceptor';
import { HALEndpointService } from './shared/hal-endpoint.service'; import { HALEndpointService } from './shared/hal-endpoint.service';
import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service'; import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service';
@@ -80,7 +80,8 @@ import { NormalizedObjectBuildService } from './cache/builders/normalized-object
import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service'; import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
import { ObjectUpdatesService } from './data/object-updates/object-updates.service'; import { ObjectUpdatesService } from './data/object-updates/object-updates.service';
import { DefaultChangeAnalyzer } from './data/default-change-analyzer.service'; import { DefaultChangeAnalyzer } from './data/default-change-analyzer.service';
import { SearchService } from '../+search-page/search-service/search.service'; import { SearchService } from './shared/search/search.service';
import { RelationshipService } from './data/relationship.service';
import { NormalizedCollection } from './cache/models/normalized-collection.model'; import { NormalizedCollection } from './cache/models/normalized-collection.model';
import { NormalizedCommunity } from './cache/models/normalized-community.model'; import { NormalizedCommunity } from './cache/models/normalized-community.model';
import { NormalizedDSpaceObject } from './cache/models/normalized-dspace-object.model'; import { NormalizedDSpaceObject } from './cache/models/normalized-dspace-object.model';
@@ -101,7 +102,6 @@ import { NormalizedSubmissionFormsModel } from './config/models/normalized-confi
import { NormalizedSubmissionSectionModel } from './config/models/normalized-config-submission-section.model'; import { NormalizedSubmissionSectionModel } from './config/models/normalized-config-submission-section.model';
import { NormalizedAuthStatus } from './auth/models/normalized-auth-status.model'; import { NormalizedAuthStatus } from './auth/models/normalized-auth-status.model';
import { NormalizedAuthorityValue } from './integration/models/normalized-authority-value.model'; import { NormalizedAuthorityValue } from './integration/models/normalized-authority-value.model';
import { RelationshipService } from './data/relationship.service';
import { RoleService } from './roles/role.service'; import { RoleService } from './roles/role.service';
import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard'; import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard';
import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service'; import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service';
@@ -124,6 +124,31 @@ import { ObjectSelectService } from '../shared/object-select/object-select.servi
import { SiteDataService } from './data/site-data.service'; import { SiteDataService } from './data/site-data.service';
import { NormalizedSite } from './cache/models/normalized-site.model'; import { NormalizedSite } from './cache/models/normalized-site.model';
import {
MOCK_RESPONSE_MAP,
MockResponseMap,
mockResponseMap
} from '../shared/mocks/dspace-rest-v2/mocks/mock-response-map';
import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
import { ENV_CONFIG, GLOBAL_CONFIG, GlobalConfig } from '../../config';
import { SearchFilterService } from './shared/search/search-filter.service';
import { SearchConfigurationService } from './shared/search/search-configuration.service';
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
import { RelationshipTypeService } from './data/relationship-type.service';
import { SidebarService } from '../shared/sidebar/sidebar.service';
/**
* When not in production, endpoint responses can be mocked for testing purposes
* If there is no mock version available for the endpoint, the actual REST response will be used just like in production mode
*/
export const restServiceFactory = (cfg: GlobalConfig, mocks: MockResponseMap, http: HttpClient) => {
if (ENV_CONFIG.production) {
return new DSpaceRESTv2Service(http);
} else {
return new EndpointMockingRestService(cfg, mocks, http);
}
};
const IMPORTS = [ const IMPORTS = [
CommonModule, CommonModule,
StoreModule.forFeature('core', coreReducers, {}), StoreModule.forFeature('core', coreReducers, {}),
@@ -143,7 +168,8 @@ const PROVIDERS = [
CollectionDataService, CollectionDataService,
SiteDataService, SiteDataService,
DSOResponseParsingService, DSOResponseParsingService,
DSpaceRESTv2Service, { provide: MOCK_RESPONSE_MAP, useValue: mockResponseMap },
{ provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [GLOBAL_CONFIG, MOCK_RESPONSE_MAP, HttpClient]},
DynamicFormLayoutService, DynamicFormLayoutService,
DynamicFormService, DynamicFormService,
DynamicFormValidationService, DynamicFormValidationService,
@@ -214,6 +240,13 @@ const PROVIDERS = [
TaskResponseParsingService, TaskResponseParsingService,
ClaimedTaskDataService, ClaimedTaskDataService,
PoolTaskDataService, PoolTaskDataService,
SearchService,
SidebarService,
SearchFilterService,
SearchFilterService,
SearchConfigurationService,
SelectableListService,
RelationshipTypeService,
// register AuthInterceptor as HttpInterceptor // register AuthInterceptor as HttpInterceptor
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,

View File

@@ -25,8 +25,8 @@ import { ResponseParsingService } from './parsing.service';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
import { SearchParam } from '../cache/models/search-param.model'; import { SearchParam } from '../cache/models/search-param.model';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
@Injectable() @Injectable()
export class CollectionDataService extends ComColDataService<Collection> { export class CollectionDataService extends ComColDataService<Collection> {
@@ -71,7 +71,9 @@ export class CollectionDataService extends ComColDataService<Collection> {
*/ */
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> { getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
const searchHref = 'findAuthorizedByCommunity'; const searchHref = 'findAuthorizedByCommunity';
options.searchParams = [new SearchParam('uuid', communityId)]; options = Object.assign({}, options, {
searchParams: [new SearchParam('uuid', communityId)]
});
return this.searchBy(searchHref, options).pipe( return this.searchBy(searchHref, options).pipe(
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending)); filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));

View File

@@ -16,6 +16,7 @@ import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
import * as uuidv4 from 'uuid/v4';
const endpoint = 'https://rest.api/core'; const endpoint = 'https://rest.api/core';
@@ -51,10 +52,11 @@ class DummyChangeAnalyzer implements ChangeAnalyzer<NormalizedTestObject> {
} }
} }
describe('DataService', () => { describe('DataService', () => {
let service: TestService; let service: TestService;
let options: FindListOptions; let options: FindListOptions;
const requestService = {} as RequestService; const requestService = {generateRequestId: () => uuidv4()} as RequestService;
const halService = {} as HALEndpointService; const halService = {} as HALEndpointService;
const rdbService = {} as RemoteDataBuildService; const rdbService = {} as RemoteDataBuildService;
const notificationsService = {} as NotificationsService; const notificationsService = {} as NotificationsService;
@@ -87,6 +89,7 @@ describe('DataService', () => {
comparator, comparator,
); );
} }
service = initTestService(); service = initTestService();
describe('getFindAllHref', () => { describe('getFindAllHref', () => {
@@ -188,7 +191,7 @@ describe('DataService', () => {
dso2.self = selfLink; dso2.self = selfLink;
dso2.metadata = [{ key: 'dc.title', value: name2 }]; dso2.metadata = [{ key: 'dc.title', value: name2 }];
spyOn(service, 'findById').and.returnValues(observableOf(dso)); spyOn(service, 'findByHref').and.returnValues(observableOf(dso));
spyOn(objectCache, 'getObjectBySelfLink').and.returnValues(observableOf(dso)); spyOn(objectCache, 'getObjectBySelfLink').and.returnValues(observableOf(dso));
spyOn(objectCache, 'addPatch'); spyOn(objectCache, 'addPatch');
}); });

View File

@@ -1,7 +1,7 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, filter, find, first, map, mergeMap, switchMap, take } from 'rxjs/operators'; import { distinctUntilChanged, filter, find, first, map, mergeMap, skipWhile, switchMap, take, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
@@ -204,15 +204,22 @@ export abstract class DataService<T extends CacheableObject> {
const hrefObs = this.getSearchByHref(searchMethod, options); const hrefObs = this.getSearchByHref(searchMethod, options);
hrefObs.pipe( return hrefObs.pipe(
first((href: string) => hasValue(href))) find((href: string) => hasValue(href)),
.subscribe((href: string) => { tap((href: string) => {
this.requestService.removeByHrefSubstring(href);
const request = new FindListRequest(this.requestService.generateRequestId(), href, options); const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
request.responseMsToLive = 10 * 1000; request.responseMsToLive = 10 * 1000;
this.requestService.configure(request);
});
return this.rdbService.buildList<T>(hrefObs) as Observable<RemoteData<PaginatedList<T>>>; this.requestService.configure(request);
}
),
switchMap((href) => this.requestService.getByHref(href)),
skipWhile((requestEntry) => hasValue(requestEntry) && requestEntry.completed),
switchMap((href) =>
this.rdbService.buildList<T>(hrefObs) as Observable<RemoteData<PaginatedList<T>>>
)
);
} }
/** /**
@@ -236,7 +243,7 @@ export abstract class DataService<T extends CacheableObject> {
if (isNotEmpty(operations)) { if (isNotEmpty(operations)) {
this.objectCache.addPatch(object.self, operations); this.objectCache.addPatch(object.self, operations);
} }
return this.findById(object.uuid); return this.findByHref(object.self);
} }
)); ));

View File

@@ -4,6 +4,7 @@ import { ChangeAnalyzer } from './change-analyzer';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
/** /**
* A class to determine what differs between two * A class to determine what differs between two
@@ -11,6 +12,8 @@ import { NormalizedObject } from '../cache/models/normalized-object.model';
*/ */
@Injectable() @Injectable()
export class DefaultChangeAnalyzer<T extends CacheableObject> implements ChangeAnalyzer<T> { export class DefaultChangeAnalyzer<T extends CacheableObject> implements ChangeAnalyzer<T> {
constructor(private normalizeService: NormalizedObjectBuildService) {
}
/** /**
* Compare the metadata of two CacheableObject and return the differences as * Compare the metadata of two CacheableObject and return the differences as
@@ -22,6 +25,6 @@ export class DefaultChangeAnalyzer<T extends CacheableObject> implements ChangeA
* The second object to compare * The second object to compare
*/ */
diff(object1: T | NormalizedObject<T>, object2: T | NormalizedObject<T>): Operation[] { diff(object1: T | NormalizedObject<T>, object2: T | NormalizedObject<T>): Operation[] {
return compare(object1, object2); return compare(this.normalizeService.normalize(object1), this.normalizeService.normalize(object2));
} }
} }

View File

@@ -7,7 +7,7 @@ import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models'; import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../shared/search/search-filter-config.model';
import { BaseResponseParsingService } from './base-response-parsing.service'; import { BaseResponseParsingService } from './base-response-parsing.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface'; import { GlobalConfig } from '../../../config/global-config.interface';

View File

@@ -9,7 +9,7 @@ import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models'; import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { FacetValue } from '../../+search-page/search-service/facet-value.model'; import { FacetValue } from '../../shared/search/facet-value.model';
import { BaseResponseParsingService } from './base-response-parsing.service'; import { BaseResponseParsingService } from './base-response-parsing.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface'; import { GlobalConfig } from '../../../config/global-config.interface';

View File

@@ -4,7 +4,7 @@ import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models'; import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { FacetValue } from '../../+search-page/search-service/facet-value.model'; import { FacetValue } from '../../shared/search/facet-value.model';
import { BaseResponseParsingService } from './base-response-parsing.service'; import { BaseResponseParsingService } from './base-response-parsing.service';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { GLOBAL_CONFIG } from '../../../config'; import { GLOBAL_CONFIG } from '../../../config';

View File

@@ -6,7 +6,7 @@ import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model'; import { SearchQueryResponse } from '../../shared/search/search-query-response.model';
import { MetadataMap, MetadataValue } from '../shared/metadata.models'; import { MetadataMap, MetadataValue } from '../shared/metadata.models';
@Injectable() @Injectable()

View File

@@ -3,7 +3,7 @@ import { hasValue } from '../../shared/empty.util';
export class PaginatedList<T> { export class PaginatedList<T> {
constructor(private pageInfo: PageInfo, constructor(public pageInfo: PageInfo,
public page: T[]) { public page: T[]) {
} }

View File

@@ -0,0 +1,99 @@
import { RequestService } from './request.service';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { PaginatedList } from './paginated-list';
import { PageInfo } from '../shared/page-info.model';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
import { RelationshipTypeService } from './relationship-type.service';
import { of as observableOf } from 'rxjs';
import { ItemType } from '../shared/item-relationships/item-type.model';
describe('RelationshipTypeService', () => {
let service: RelationshipTypeService;
let requestService: RequestService;
let restEndpointURL;
let halService: any;
let publicationTypeString;
let personTypeString;
let orgUnitTypeString;
let publicationType;
let personType;
let orgUnitType;
let relationshipType1;
let relationshipType2;
let buildList;
let rdbService;
function init() {
restEndpointURL = 'https://rest.api/relationshiptypes';
halService = new HALEndpointServiceStub(restEndpointURL);
publicationTypeString = 'Publication';
personTypeString = 'Person';
orgUnitTypeString = 'OrgUnit';
publicationType = Object.assign(new ItemType(), {label: publicationTypeString});
personType = Object.assign(new ItemType(), {label: personTypeString});
orgUnitType = Object.assign(new ItemType(), {label: orgUnitTypeString});
relationshipType1 = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
leftwardType: 'isAuthorOfPublication',
rightwardType: 'isPublicationOfAuthor',
leftType: createSuccessfulRemoteDataObject$(publicationType),
rightType: createSuccessfulRemoteDataObject$(personType)
});
relationshipType2 = Object.assign(new RelationshipType(), {
id: '2',
uuid: '2',
leftwardType: 'isOrgUnitOfPublication',
rightwardType: 'isPublicationOfOrgUnit',
leftType: createSuccessfulRemoteDataObject$(publicationType),
rightType: createSuccessfulRemoteDataObject$(orgUnitType)
});
buildList = createSuccessfulRemoteDataObject(new PaginatedList(new PageInfo(), [relationshipType1, relationshipType2]));
rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
}
function initTestService() {
return new RelationshipTypeService(
requestService,
halService,
rdbService
);
}
beforeEach(() => {
init();
requestService = getMockRequestService();
service = initTestService();
});
describe('getAllRelationshipTypes', () => {
it('should return all relationshipTypes', (done) => {
const expected = service.getAllRelationshipTypes({});
expected.subscribe((e) => {
expect(e).toBe(buildList);
done();
})
});
});
describe('getRelationshipTypeByLabelAndTypes', () => {
it('should return the type filtered by label and type strings', (done) => {
const expected = service.getRelationshipTypeByLabelAndTypes(relationshipType1.leftwardType, publicationTypeString, personTypeString);
expected.subscribe((e) => {
expect(e).toBe(relationshipType1);
done();
})
});
});
});

View File

@@ -0,0 +1,81 @@
import { Injectable } from '@angular/core';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { filter, find, map, switchMap, tap } from 'rxjs/operators';
import { configureRequest, getSucceededRemoteData } from '../shared/operators';
import { Observable } from 'rxjs/internal/Observable';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list';
import { combineLatest as observableCombineLatest } from 'rxjs';
import { ItemType } from '../shared/item-relationships/item-type.model';
import { isNotUndefined } from '../../shared/empty.util';
import { FindListOptions, FindListRequest } from './request.models';
/**
* The service handling all relationship requests
*/
@Injectable()
export class RelationshipTypeService {
protected linkPath = 'relationshiptypes';
constructor(protected requestService: RequestService,
protected halService: HALEndpointService,
protected rdbService: RemoteDataBuildService) {
}
/**
* Get the endpoint for a relationship type by ID
* @param id
*/
getRelationshipTypeEndpoint(id: number) {
return this.halService.getEndpoint(this.linkPath).pipe(
map((href: string) => `${href}/${id}`)
);
}
getAllRelationshipTypes(options: FindListOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
const link$ = this.halService.getEndpoint(this.linkPath);
return link$
.pipe(
map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
configureRequest(this.requestService),
switchMap(() => this.rdbService.buildList(link$))
);
}
/**
* Get the RelationshipType for a relationship type by label
* @param label
*/
getRelationshipTypeByLabelAndTypes(label: string, firstType: string, secondType: string): Observable<RelationshipType> {
return this.getAllRelationshipTypes({ currentPage: 1, elementsPerPage: Number.MAX_VALUE })
.pipe(
getSucceededRemoteData(),
/* Flatten the page so we can treat it like an observable */
switchMap((typeListRD: RemoteData<PaginatedList<RelationshipType>>) => typeListRD.payload.page),
switchMap((type: RelationshipType) => {
if (type.leftwardType === label) {
return this.checkType(type, firstType, secondType);
} else if (type.rightwardType === label) {
return this.checkType(type, secondType, firstType);
} else {
return [];
}
}),
);
}
// Check if relationship type matches the given types
// returns a void observable if there's not match
// returns an observable that emits the relationship type when there is a match
private checkType(type: RelationshipType, firstType: string, secondType: string): Observable<RelationshipType> {
const entityTypes = observableCombineLatest(type.leftType.pipe(getSucceededRemoteData()), type.rightType.pipe(getSucceededRemoteData()));
return entityTypes.pipe(
find(([leftTypeRD, rightTypeRD]: [RemoteData<ItemType>, RemoteData<ItemType>]) => leftTypeRD.payload.label === firstType && rightTypeRD.payload.label === secondType),
filter((types) => isNotUndefined(types)),
map(() => type)
);
}
}

View File

@@ -71,12 +71,14 @@ describe('RelationshipService', () => {
const rdbService = getMockRemoteDataBuildService(undefined, buildList$); const rdbService = getMockRemoteDataBuildService(undefined, buildList$);
const objectCache = Object.assign({ const objectCache = Object.assign({
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
remove: () => {} remove: () => {},
hasBySelfLinkObservable: () => observableOf(false)
/* tslint:enable:no-empty */ /* tslint:enable:no-empty */
}) as ObjectCacheService; }) as ObjectCacheService;
const itemService = jasmine.createSpyObj('itemService', { const itemService = jasmine.createSpyObj('itemService', {
findById: (uuid) => new RemoteData(false, false, true, undefined, relatedItems.filter((relatedItem) => relatedItem.id === uuid)[0]) findById: (uuid) => new RemoteData(false, false, true, undefined, relatedItems.find((relatedItem) => relatedItem.id === uuid)),
findByHref: createSuccessfulRemoteDataObject$(relatedItems[0])
}); });
function initTestService() { function initTestService() {
@@ -90,6 +92,7 @@ describe('RelationshipService', () => {
objectCache, objectCache,
null, null,
null, null,
null,
null null
); );
} }
@@ -133,14 +136,6 @@ describe('RelationshipService', () => {
}); });
}); });
describe('getItemRelationshipLabels', () => {
it('should return the correct labels', () => {
service.getItemRelationshipLabels(item).subscribe((result) => {
expect(result).toEqual([relationshipType.rightwardType]);
});
});
});
describe('getRelatedItems', () => { describe('getRelatedItems', () => {
it('should return the related items', () => { it('should return the related items', () => {
service.getRelatedItems(item).subscribe((result) => { service.getRelatedItems(item).subscribe((result) => {

View File

@@ -2,38 +2,43 @@ import { Injectable } from '@angular/core';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { hasValue, hasValueOperator, isNotEmptyOperator } from '../../shared/empty.util'; import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { distinctUntilChanged, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, mergeMap, skipWhile, startWith, switchMap, take, tap } from 'rxjs/operators';
import { import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
configureRequest, import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
filterSuccessfulResponses,
getRemoteDataPayload, getResponseFromEntry,
getSucceededRemoteData
} from '../shared/operators';
import { DeleteRequest, FindListOptions, RestRequest } from './request.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../cache/response.models'; import { RestResponse } from '../cache/response.models';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
import { Relationship } from '../shared/item-relationships/relationship.model'; import { Relationship } from '../shared/item-relationships/relationship.model';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import { RemoteData } from './remote-data'; import { RemoteData } from './remote-data';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
import { zip as observableZip } from 'rxjs';
import { PaginatedList } from './paginated-list'; import { PaginatedList } from './paginated-list';
import { ItemDataService } from './item-data.service'; import { ItemDataService } from './item-data.service';
import { import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
compareArraysUsingIds, filterRelationsByTypeLabel, paginatedRelationsToItems,
relationsToItems
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { DataService } from './data.service'; import { DataService } from './data.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { Store } from '@ngrx/store'; import { MemoizedSelector, select, Store } from '@ngrx/store';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { SearchParam } from '../cache/models/search-param.model'; import { SearchParam } from '../cache/models/search-param.model';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AppState, keySelector } from '../../app.reducer';
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
const relationshipListStateSelector = (listID: string): MemoizedSelector<AppState, NameVariantListState> => {
return keySelector<NameVariantListState>(listID, relationshipListsStateSelector);
};
const relationshipStateSelector = (listID: string, itemID: string): MemoizedSelector<AppState, string> => {
return keySelector<string>(itemID, relationshipListStateSelector(listID));
};
/** /**
* The service handling all relationship requests * The service handling all relationship requests
@@ -52,7 +57,8 @@ export class RelationshipService extends DataService<Relationship> {
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected http: HttpClient, protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Relationship>) { protected comparator: DefaultChangeAnalyzer<Relationship>,
protected appStore: Store<AppState>) {
super(); super();
} }
@@ -65,76 +71,94 @@ export class RelationshipService extends DataService<Relationship> {
* @param uuid * @param uuid
*/ */
getRelationshipEndpoint(uuid: string) { getRelationshipEndpoint(uuid: string) {
return this.halService.getEndpoint(this.linkPath).pipe( return this.getBrowseEndpoint().pipe(
map((href: string) => `${href}/${uuid}`) map((href: string) => `${href}/${uuid}`)
); );
} }
/**
* Find a relationship by its UUID
* @param uuid
*/
findById(uuid: string): Observable<RemoteData<Relationship>> {
const href$ = this.getRelationshipEndpoint(uuid);
return this.rdbService.buildSingle<Relationship>(href$);
}
/** /**
* Send a delete request for a relationship by ID * Send a delete request for a relationship by ID
* @param uuid * @param id
*/ */
deleteRelationship(uuid: string): Observable<RestResponse> { deleteRelationship(id: string): Observable<RestResponse> {
return this.getRelationshipEndpoint(uuid).pipe( return this.getRelationshipEndpoint(id).pipe(
isNotEmptyOperator(), isNotEmptyOperator(),
distinctUntilChanged(), take(1),
map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)),
configureRequest(this.requestService), configureRequest(this.requestService),
switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)), switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
getResponseFromEntry(), getResponseFromEntry(),
tap(() => this.clearRelatedCache(uuid)) tap(() => this.removeRelationshipItemsFromCacheByRelationship(id))
); );
} }
/** /**
* Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types * Method to create a new relationship
* This is used for easier access of a relationship's type because they exist as observables * @param typeId The identifier of the relationship type
* @param item * @param item1 The first item of the relationship
* @param item2 The second item of the relationship
* @param leftwardValue The leftward value of the relationship
* @param rightwardValue The rightward value of the relationship
*/ */
getItemResolvedRelsAndTypes(item: Item): Observable<[Relationship[], RelationshipType[]]> { addRelationship(typeId: string, item1: Item, item2: Item, leftwardValue?: string, rightwardValue?: string): Observable<RestResponse> {
return observableCombineLatest( const options: HttpOptions = Object.create({});
this.getItemRelationshipsArray(item), let headers = new HttpHeaders();
this.getItemRelationshipTypesArray(item) headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
return this.halService.getEndpoint(this.linkPath).pipe(
isNotEmptyOperator(),
take(1),
map((endpointUrl: string) => `${endpointUrl}?relationshipType=${typeId}`),
map((endpointUrl: string) => isNotEmpty(leftwardValue) ? `${endpointUrl}&leftwardValue=${leftwardValue}` : endpointUrl),
map((endpointUrl: string) => isNotEmpty(rightwardValue) ? `${endpointUrl}&rightwardValue=${rightwardValue}` : endpointUrl),
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, `${item1.self} \n ${item2.self}`, options)),
configureRequest(this.requestService),
switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)),
getResponseFromEntry(),
tap(() => this.removeRelationshipItemsFromCache(item1)),
tap(() => this.removeRelationshipItemsFromCache(item2))
); );
} }
/** /**
* Get a combined observable containing an array of all the item's relationship's left- and right-side items, as well as an array of the relationships their types * Method to remove two items of a relationship from the cache using the identifier of the relationship
* This is used for easier access of a relationship's type and left and right items because they exist as observables * @param relationshipId The identifier of the relationship
* @param item
*/ */
getItemResolvedRelatedItemsAndTypes(item: Item): Observable<[Item[], Item[], RelationshipType[]]> { private removeRelationshipItemsFromCacheByRelationship(relationshipId: string) {
return observableCombineLatest( this.findById(relationshipId).pipe(
this.getItemLeftRelatedItemArray(item), getSucceededRemoteData(),
this.getItemRightRelatedItemArray(item), getRemoteDataPayload(),
this.getItemRelationshipTypesArray(item) switchMap((relationship: Relationship) => combineLatest(
); relationship.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
relationship.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload())
)
),
take(1)
).subscribe(([item1, item2]) => {
this.removeRelationshipItemsFromCache(item1);
this.removeRelationshipItemsFromCache(item2);
})
} }
/** /**
* Get a combined observable containing an array of all the item's relationship's left- and right-side items, as well as an array of the relationships themselves * Method to remove an item that's part of a relationship from the cache
* This is used for easier access of the relationship and their left and right items because they exist as observables * @param item The item to remove from the cache
* @param item
*/ */
getItemResolvedRelatedItemsAndRelationships(item: Item): Observable<[Item[], Item[], Relationship[]]> { private removeRelationshipItemsFromCache(item) {
return observableCombineLatest( this.objectCache.remove(item.self);
this.getItemLeftRelatedItemArray(item), this.requestService.removeByHrefSubstring(item.self);
this.getItemRightRelatedItemArray(item), combineLatest(
this.getItemRelationshipsArray(item) this.objectCache.hasBySelfLinkObservable(item.self),
); this.requestService.hasByHrefObservable(item.self)
).pipe(
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
take(1),
switchMap(() => this.itemService.findByHref(item.self).pipe(take(1)))
).subscribe();
} }
/** /**
* Get an item their relationships in the form of an array * Get an item its relationships in the form of an array
* @param item * @param item
*/ */
getItemRelationshipsArray(item: Item): Observable<Relationship[]> { getItemRelationshipsArray(item: Item): Observable<Relationship[]> {
@@ -148,67 +172,33 @@ export class RelationshipService extends DataService<Relationship> {
} }
/** /**
* Get an item their relationship types in the form of an array * Get an array of the labels of an items unique relationship types
* @param item
*/
getItemRelationshipTypesArray(item: Item): Observable<RelationshipType[]> {
return this.getItemRelationshipsArray(item).pipe(
flatMap((rels: Relationship[]) =>
observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe(
map(([...arr]: Array<RemoteData<RelationshipType>>) => arr.map((d: RemoteData<RelationshipType>) => d.payload).filter((type) => hasValue(type))),
filter((arr) => arr.length === rels.length)
)
),
distinctUntilChanged(compareArraysUsingIds())
);
}
/**
* Get an item his relationship's left-side related items in the form of an array
* @param item
*/
getItemLeftRelatedItemArray(item: Item): Observable<Item[]> {
return this.getItemRelationshipsArray(item).pipe(
flatMap((rels: Relationship[]) => observableZip(...rels.map((rel: Relationship) => rel.leftItem)).pipe(
map(([...arr]: Array<RemoteData<Item>>) => arr.map((rd: RemoteData<Item>) => rd.payload).filter((i) => hasValue(i))),
filter((arr) => arr.length === rels.length)
)),
distinctUntilChanged(compareArraysUsingIds())
);
}
/**
* Get an item his relationship's right-side related items in the form of an array
* @param item
*/
getItemRightRelatedItemArray(item: Item): Observable<Item[]> {
return this.getItemRelationshipsArray(item).pipe(
flatMap((rels: Relationship[]) => observableZip(...rels.map((rel: Relationship) => rel.rightItem)).pipe(
map(([...arr]: Array<RemoteData<Item>>) => arr.map((rd: RemoteData<Item>) => rd.payload).filter((i) => hasValue(i))),
filter((arr) => arr.length === rels.length)
)),
distinctUntilChanged(compareArraysUsingIds())
);
}
/**
* Get an array of an item their unique relationship type's labels
* The array doesn't contain any duplicate labels * The array doesn't contain any duplicate labels
* @param item * @param item
*/ */
getItemRelationshipLabels(item: Item): Observable<string[]> { getRelationshipTypeLabelsByItem(item: Item): Observable<string[]> {
return this.getItemResolvedRelatedItemsAndTypes(item).pipe( return this.getItemRelationshipsArray(item).pipe(
map(([leftItems, rightItems, relTypesCurrentPage]) => { switchMap((relationships: Relationship[]) => observableCombineLatest(relationships.map((relationship: Relationship) => this.getRelationshipTypeLabelByRelationshipAndItem(relationship, item)))),
return relTypesCurrentPage.map((type, index) => {
if (leftItems[index].uuid === item.uuid) {
return type.leftwardType;
} else {
return type.rightwardType;
}
});
}),
map((labels: string[]) => Array.from(new Set(labels))) map((labels: string[]) => Array.from(new Set(labels)))
);
}
private getRelationshipTypeLabelByRelationshipAndItem(relationship: Relationship, item: Item): Observable<string> {
return relationship.leftItem.pipe(
getSucceededRemoteData(),
map((itemRD: RemoteData<Item>) => itemRD.payload),
switchMap((otherItem: Item) => relationship.relationshipType.pipe(
getSucceededRemoteData(),
map((relationshipTypeRD) => relationshipTypeRD.payload),
map((relationshipType: RelationshipType) => {
if (otherItem.uuid === item.uuid) {
return relationshipType.leftwardType;
} else {
return relationshipType.rightwardType;
}
})
) )
))
} }
/** /**
@@ -244,7 +234,7 @@ export class RelationshipService extends DataService<Relationship> {
if (options) { if (options) {
findListOptions = Object.assign(new FindListOptions(), options); findListOptions = Object.assign(new FindListOptions(), options);
} }
const searchParams = [ new SearchParam('label', label), new SearchParam('dso', item.id) ]; const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
if (findListOptions.searchParams) { if (findListOptions.searchParams) {
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams]; findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
} else { } else {
@@ -254,20 +244,146 @@ export class RelationshipService extends DataService<Relationship> {
} }
/** /**
* Clear object and request caches of the items related to a relationship (left and right items) * Method for fetching an item's relationships, but filtered by related item IDs (essentially performing a reverse lookup)
* @param uuid * Only relationships where leftItem or rightItem's ID is present in the list provided will be returned
* @param item
* @param uuids
*/ */
clearRelatedCache(uuid: string) { getRelationshipsByRelatedItemIds(item: Item, uuids: string[]): Observable<Relationship[]> {
this.findById(uuid).pipe( return this.getItemRelationshipsArray(item).pipe(
switchMap((relationships: Relationship[]) => {
return observableCombineLatest(...relationships.map((relationship: Relationship) => {
const isLeftItem$ = this.isItemInUUIDArray(relationship.leftItem, uuids);
const isRightItem$ = this.isItemInUUIDArray(relationship.rightItem, uuids);
return observableCombineLatest(isLeftItem$, isRightItem$).pipe(
filter(([isLeftItem, isRightItem]) => isLeftItem || isRightItem),
map(() => relationship),
startWith(undefined)
);
}))
}),
map((relationships: Relationship[]) => relationships.filter(((relationship) => hasValue(relationship)))),
)
}
private isItemInUUIDArray(itemRD$: Observable<RemoteData<Item>>, uuids: string[]) {
return itemRD$.pipe(
getSucceededRemoteData(), getSucceededRemoteData(),
flatMap((rd: RemoteData<Relationship>) => observableCombineLatest(rd.payload.leftItem.pipe(getSucceededRemoteData()), rd.payload.rightItem.pipe(getSucceededRemoteData()))), map((itemRD: RemoteData<Item>) => itemRD.payload),
take(1) map((item: Item) => uuids.includes(item.uuid))
).subscribe(([leftItem, rightItem]) => { );
this.objectCache.remove(leftItem.payload.self); }
this.objectCache.remove(rightItem.payload.self);
this.requestService.removeByHrefSubstring(leftItem.payload.self); /**
this.requestService.removeByHrefSubstring(rightItem.payload.self); * Method to retrieve a relationship based on two items and a relationship type label
}); * @param item1 The first item in the relationship
* @param item2 The second item in the relationship
* @param label The rightward or leftward type of the relationship
*/
getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string): Observable<Relationship> {
return this.getItemRelationshipsByLabel(item1, label)
.pipe(
getSucceededRemoteData(),
isNotEmptyOperator(),
map((relationshipListRD: RemoteData<PaginatedList<Relationship>>) => relationshipListRD.payload.page),
mergeMap((relationships: Relationship[]) => {
return observableCombineLatest(...relationships.map((relationship: Relationship) => {
return observableCombineLatest(
this.isItemMatchWithItemRD(relationship.leftItem, item2),
this.isItemMatchWithItemRD(relationship.rightItem, item2)
).pipe(
map(([isLeftItem, isRightItem]) => isLeftItem || isRightItem),
map((isMatch) => isMatch ? relationship : undefined)
);
}))
}),
map((relationships: Relationship[]) => relationships.find(((relationship) => hasValue(relationship))))
)
}
private isItemMatchWithItemRD(itemRD$: Observable<RemoteData<Item>>, itemCheck: Item): Observable<boolean> {
return itemRD$.pipe(
getSucceededRemoteData(),
map((itemRD: RemoteData<Item>) => itemRD.payload),
map((item: Item) => item.uuid === itemCheck.uuid)
);
}
/**
* Method to set the name variant for specific list and item
* @param listID The list for which to save the name variant
* @param itemID The item ID for which to save the name variant
* @param nameVariant The name variant to save
*/
public setNameVariant(listID: string, itemID: string, nameVariant: string) {
this.appStore.dispatch(new SetNameVariantAction(listID, itemID, nameVariant));
}
/**
* Method to retrieve the name variant for a specific list and item
* @param listID The list for which to retrieve the name variant
* @param itemID The item ID for which to retrieve the name variant
*/
public getNameVariant(listID: string, itemID: string): Observable<string> {
return this.appStore.pipe(
select(relationshipStateSelector(listID, itemID))
);
}
/**
* Method to remove the name variant for specific list and item
* @param listID The list for which to remove the name variant
* @param itemID The item ID for which to remove the name variant
*/
public removeNameVariant(listID: string, itemID: string) {
this.appStore.dispatch(new RemoveNameVariantAction(listID, itemID));
}
/**
* Method to retrieve all name variants for a single list
* @param listID The id of the list
*/
public getNameVariantsByListID(listID: string) {
return this.appStore.pipe(select(relationshipListStateSelector(listID)));
}
/**
* Method to update the name variant on the server
* @param item1 The first item of the relationship
* @param item2 The second item of the relationship
* @param relationshipLabel The leftward or rightward type of the relationship
* @param nameVariant The name variant to set for the matching relationship
*/
public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable<RemoteData<Relationship>> {
return this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
.pipe(
switchMap((relation: Relationship) =>
relation.relationshipType.pipe(
getSucceededRemoteData(),
getRemoteDataPayload(),
map((type) => {
return { relation, type }
})
)
),
switchMap((relationshipAndType: { relation: Relationship, type: RelationshipType }) => {
const { relation, type } = relationshipAndType;
let updatedRelationship;
if (relationshipLabel === type.leftwardType) {
updatedRelationship = Object.assign(new Relationship(), relation, { rightwardValue: nameVariant });
} else {
updatedRelationship = Object.assign(new Relationship(), relation, { leftwardValue: nameVariant });
}
return this.update(updatedRelationship);
}),
// skipWhile((relationshipRD: RemoteData<Relationship>) => !relationshipRD.isSuccessful)
tap((relationshipRD: RemoteData<Relationship>) => {
if (relationshipRD.hasSucceeded) {
this.removeRelationshipItemsFromCache(item1);
this.removeRelationshipItemsFromCache(item2);
}
}),
)
} }
} }

View File

@@ -13,11 +13,11 @@ export enum RemoteDataState {
*/ */
export class RemoteData<T> { export class RemoteData<T> {
constructor( constructor(
private requestPending: boolean, private requestPending?: boolean,
private responsePending: boolean, private responsePending?: boolean,
private isSuccessful: boolean, private isSuccessful?: boolean,
public error: RemoteDataError, public error?: RemoteDataError,
public payload: T public payload?: T
) { ) {
} }

View File

@@ -3,7 +3,7 @@ import { HttpHeaders } from '@angular/common/http';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable, race as observableRace } from 'rxjs'; import { Observable, race as observableRace } from 'rxjs';
import { filter, map, mergeMap, take } from 'rxjs/operators'; import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
import { cloneDeep, remove } from 'lodash'; import { cloneDeep, remove } from 'lodash';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
@@ -304,6 +304,7 @@ export class RequestService {
*/ */
hasByHref(href: string): boolean { hasByHref(href: string): boolean {
let result = false; let result = false;
/* NB: that this is only a solution because the select method is synchronous, see: https://github.com/ngrx/store/issues/296#issuecomment-269032571*/
this.getByHref(href).pipe( this.getByHref(href).pipe(
take(1) take(1)
).subscribe((requestEntry: RequestEntry) => result = this.isValid(requestEntry)); ).subscribe((requestEntry: RequestEntry) => result = this.isValid(requestEntry));

View File

@@ -6,7 +6,7 @@ import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model'; import { SearchQueryResponse } from '../../shared/search/search-query-response.model';
import { MetadataMap, MetadataValue } from '../shared/metadata.models'; import { MetadataMap, MetadataValue } from '../shared/metadata.models';
@Injectable() @Injectable()

View File

@@ -26,7 +26,7 @@ export interface HttpOptions {
@Injectable() @Injectable()
export class DSpaceRESTv2Service { export class DSpaceRESTv2Service {
constructor(private http: HttpClient) { constructor(protected http: HttpClient) {
} }

View File

@@ -9,6 +9,7 @@ import { Group } from './group.model';
@mapsTo(EPerson) @mapsTo(EPerson)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
export class NormalizedEPerson extends NormalizedDSpaceObject<EPerson> implements CacheableObject { export class NormalizedEPerson extends NormalizedDSpaceObject<EPerson> implements CacheableObject {
/** /**
* A string representing the unique handle of this EPerson * A string representing the unique handle of this EPerson
*/ */

View File

@@ -7,7 +7,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
/** /**
* Class the represents a metadata field * Class the represents a metadata field
*/ */
export class MetadataField implements ListableObject { export class MetadataField extends ListableObject {
static type = new ResourceType('metadatafield'); static type = new ResourceType('metadatafield');
/** /**

View File

@@ -5,7 +5,7 @@ import { GenericConstructor } from '../shared/generic-constructor';
/** /**
* Class that represents a metadata schema * Class that represents a metadata schema
*/ */
export class MetadataSchema implements ListableObject { export class MetadataSchema extends ListableObject {
static type = new ResourceType('metadataschema'); static type = new ResourceType('metadataschema');
/** /**

View File

@@ -2,7 +2,6 @@ import { autoserialize, deserialize, inheritSerialization } from 'cerialize';
import { mapsTo, relationship } from '../cache/builders/build-decorators'; import { mapsTo, relationship } from '../cache/builders/build-decorators';
import { MetadataField } from './metadata-field.model'; import { MetadataField } from './metadata-field.model';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { MetadataSchema } from './metadata-schema.model'; import { MetadataSchema } from './metadata-schema.model';
/** /**

View File

@@ -1,7 +1,6 @@
import { autoserialize, inheritSerialization } from 'cerialize'; import { autoserialize, inheritSerialization } from 'cerialize';
import { NormalizedObject } from '../cache/models/normalized-object.model'; import { NormalizedObject } from '../cache/models/normalized-object.model';
import { mapsTo } from '../cache/builders/build-decorators'; import { mapsTo } from '../cache/builders/build-decorators';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { MetadataSchema } from './metadata-schema.model'; import { MetadataSchema } from './metadata-schema.model';
/** /**
@@ -33,4 +32,5 @@ export class NormalizedMetadataSchema extends NormalizedObject<MetadataSchema> {
*/ */
@autoserialize @autoserialize
namespace: string; namespace: string;
} }

View File

@@ -0,0 +1,22 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
/**
* The list of HrefIndexAction type definitions
*/
export const RouterActionTypes = {
ROUTE_UPDATE: type('dspace/core/router/ROUTE_UPDATE'),
};
/* tslint:disable:max-classes-per-file */
/**
* An ngrx action to be fired when the route is updated
* Note that, contrary to the router-store.ROUTER_NAVIGATION action,
* this action will only be fired when the path changes,
* not when just the query parameters change
*/
export class RouteUpdateAction implements Action {
type = RouterActionTypes.ROUTE_UPDATE;
}
/* tslint:enable:max-classes-per-file */

View File

@@ -0,0 +1,31 @@
import { filter, map, pairwise } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects'
import * as fromRouter from '@ngrx/router-store';
import { RouterNavigationAction } from '@ngrx/router-store';
import { Router } from '@angular/router';
import { RouteUpdateAction } from './router.actions';
@Injectable()
export class RouterEffects {
/**
* Effect that fires a new RouteUpdateAction when then path of route is changed
* @type {Observable<RouteUpdateAction>}
*/
@Effect() routeChange$ = this.actions$
.pipe(
ofType(fromRouter.ROUTER_NAVIGATION),
pairwise(),
map((actions: RouterNavigationAction[]) =>
actions.map((navigateAction) => {
const urlTree = this.router.parseUrl(navigateAction.payload.routerState.url);
return urlTree.root.children.primary.segments.map((it) => it.path).join('/');
})),
filter((actions: string[]) => actions[0] !== actions[1]),
map(() => new RouteUpdateAction())
);
constructor(private actions$: Actions, private router: Router) {
}
}

View File

@@ -10,6 +10,8 @@ export const RouteActionTypes = {
SET_PARAMETERS: type('dspace/core/route/SET_PARAMETERS'), SET_PARAMETERS: type('dspace/core/route/SET_PARAMETERS'),
ADD_QUERY_PARAMETER: type('dspace/core/route/ADD_QUERY_PARAMETER'), ADD_QUERY_PARAMETER: type('dspace/core/route/ADD_QUERY_PARAMETER'),
ADD_PARAMETER: type('dspace/core/route/ADD_PARAMETER'), ADD_PARAMETER: type('dspace/core/route/ADD_PARAMETER'),
SET_QUERY_PARAMETER: type('dspace/core/route/SET_QUERY_PARAMETER'),
SET_PARAMETER: type('dspace/core/route/SET_PARAMETER'),
RESET: type('dspace/core/route/RESET'), RESET: type('dspace/core/route/RESET'),
}; };
@@ -96,6 +98,52 @@ export class AddParameterAction implements Action {
} }
} }
/**
* An ngrx action to set a query parameter
*/
export class SetQueryParameterAction implements Action {
type = RouteActionTypes.SET_QUERY_PARAMETER;
payload: {
key: string;
value: string;
};
/**
* Create a new SetQueryParameterAction
*
* @param key
* the key to set
* @param value
* the value of this key
*/
constructor(key: string, value: string) {
this.payload = { key, value };
}
}
/**
* An ngrx action to set a parameter
*/
export class SetParameterAction implements Action {
type = RouteActionTypes.SET_PARAMETER;
payload: {
key: string;
value: string;
};
/**
* Create a new SetParameterAction
*
* @param key
* the key to set
* @param value
* the value of this key
*/
constructor(key: string, value: string) {
this.payload = { key, value };
}
}
/** /**
* An ngrx action to reset the route state * An ngrx action to reset the route state
*/ */
@@ -113,4 +161,5 @@ export type RouteActions =
| SetParametersAction | SetParametersAction
| AddQueryParameterAction | AddQueryParameterAction
| AddParameterAction | AddParameterAction
| ResetRouteStateAction; | ResetRouteStateAction
| SetParameterAction;

View File

@@ -1,8 +1,9 @@
import { map } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects' import { Actions, Effect, ofType } from '@ngrx/effects'
import * as fromRouter from '@ngrx/router-store'; import { ResetRouteStateAction, RouteActionTypes } from './route.actions';
import { ResetRouteStateAction } from './route.actions'; import { RouterActionTypes } from '../../core/router/router.actions';
import { RouteService } from './route.service';
@Injectable() @Injectable()
export class RouteEffects { export class RouteEffects {
@@ -12,12 +13,16 @@ export class RouteEffects {
*/ */
@Effect() routeChange$ = this.actions$ @Effect() routeChange$ = this.actions$
.pipe( .pipe(
ofType(fromRouter.ROUTER_NAVIGATION), ofType(RouterActionTypes.ROUTE_UPDATE),
map(() => new ResetRouteStateAction()) map(() => new ResetRouteStateAction()),
); );
constructor(private actions$: Actions) { @Effect({dispatch: false }) afterResetChange$ = this.actions$
.pipe(
ofType(RouteActionTypes.RESET),
tap(() => this.service.setCurrentRouteInfo()),
);
constructor(private actions$: Actions, private service: RouteService) {
} }
} }

View File

@@ -3,7 +3,11 @@ import {
AddParameterAction, AddParameterAction,
AddQueryParameterAction, AddQueryParameterAction,
RouteActions, RouteActions,
RouteActionTypes, SetParametersAction, SetQueryParametersAction RouteActionTypes,
SetParameterAction,
SetParametersAction,
SetQueryParameterAction,
SetQueryParametersAction
} from './route.actions'; } from './route.actions';
/** /**
@@ -44,6 +48,12 @@ export function routeReducer(state = initialState, action: RouteActions): RouteS
case RouteActionTypes.ADD_QUERY_PARAMETER: { case RouteActionTypes.ADD_QUERY_PARAMETER: {
return addParameter(state, action as AddQueryParameterAction, 'queryParams'); return addParameter(state, action as AddQueryParameterAction, 'queryParams');
} }
case RouteActionTypes.SET_PARAMETER: {
return setParameter(state, action as SetParameterAction, 'params');
}
case RouteActionTypes.SET_QUERY_PARAMETER: {
return setParameter(state, action as SetQueryParameterAction, 'queryParams');
}
default: { default: {
return state; return state;
} }
@@ -60,9 +70,10 @@ function addParameter(state: RouteState, action: AddParameterAction | AddQueryPa
const subState = state[paramType]; const subState = state[paramType];
const existingValues = subState[action.payload.key] || []; const existingValues = subState[action.payload.key] || [];
const newValues = [...existingValues, action.payload.value]; const newValues = [...existingValues, action.payload.value];
const newSubstate = Object.assign(subState, { [action.payload.key]: newValues }); const newSubstate = Object.assign({}, subState, { [action.payload.key]: newValues });
return Object.assign({}, state, { [paramType]: newSubstate }); return Object.assign({}, state, { [paramType]: newSubstate });
} }
/** /**
* Set a route or query parameter in the store * Set a route or query parameter in the store
* @param state The current state * @param state The current state
@@ -70,5 +81,17 @@ function addParameter(state: RouteState, action: AddParameterAction | AddQueryPa
* @param paramType The type of parameter to set: route or query parameter * @param paramType The type of parameter to set: route or query parameter
*/ */
function setParameters(state: RouteState, action: SetParametersAction | SetQueryParametersAction, paramType: string): RouteState { function setParameters(state: RouteState, action: SetParametersAction | SetQueryParametersAction, paramType: string): RouteState {
return Object.assign({}, state, { [paramType]: action.payload }); return Object.assign({}, state, { [paramType]: { [action.payload.key]: action.payload.value } });
}
/**
* Set a route or query parameter in the store
* @param state The current state
* @param action The set action to perform on the current state
* @param paramType The type of parameter to set: route or query parameter
*/
function setParameter(state: RouteState, action: SetParameterAction | SetQueryParameterAction, paramType: string): RouteState {
const subState = state[paramType];
const newSubstate = Object.assign({}, subState, { [action.payload.key]: action.payload.value });
return Object.assign({}, state, { [paramType]: newSubstate });
} }

View File

@@ -1,4 +1,4 @@
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, take, tap } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
ActivatedRoute, ActivatedRoute,
@@ -12,12 +12,17 @@ import { combineLatest, Observable } from 'rxjs';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { AddUrlToHistoryAction } from '../../shared/history/history.actions'; import {
import { historySelector } from '../../shared/history/selectors'; AddParameterAction,
import { SetParametersAction, SetQueryParametersAction } from './route.actions'; SetParameterAction,
import { CoreState } from '../core.reducers'; SetParametersAction,
SetQueryParametersAction
} from './route.actions';
import { CoreState } from '../../core/core.reducers';
import { coreSelector } from '../../core/core.selectors';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { coreSelector } from '../core.selectors'; import { historySelector } from '../../shared/history/selectors';
import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
/** /**
* Selector to select all route parameters from the store * Selector to select all route parameters from the store
@@ -121,7 +126,7 @@ export class RouteService {
} }
getRouteDataValue(datafield: string): Observable<any> { getRouteDataValue(datafield: string): Observable<any> {
return this.route.data.pipe(map((data) => data[datafield]), distinctUntilChanged(),); return this.route.data.pipe(map((data) => data[datafield]), distinctUntilChanged());
} }
/** /**
@@ -157,11 +162,9 @@ export class RouteService {
} }
public saveRouting(): void { public saveRouting(): void {
combineLatest(this.router.events, this.getRouteParams(), this.route.queryParams) this.router.events
.pipe(filter(([event, params, queryParams]) => event instanceof NavigationEnd)) .pipe(filter((event) => event instanceof NavigationEnd))
.subscribe(([event, params, queryParams]: [NavigationEnd, Params, Params]) => { .subscribe((event: NavigationEnd) => {
this.store.dispatch(new SetParametersAction(params));
this.store.dispatch(new SetQueryParametersAction(queryParams));
this.store.dispatch(new AddUrlToHistoryAction(event.urlAfterRedirects)); this.store.dispatch(new AddUrlToHistoryAction(event.urlAfterRedirects));
}); });
} }
@@ -183,4 +186,26 @@ export class RouteService {
map((history: string[]) => history[history.length - 2] || '') map((history: string[]) => history[history.length - 2] || '')
); );
} }
public addParameter(key, value) {
this.store.dispatch(new AddParameterAction(key, value));
}
public setParameter(key, value) {
this.store.dispatch(new SetParameterAction(key, value));
}
/**
* Sets the current route parameters and query parameters in the store
*/
public setCurrentRouteInfo() {
combineLatest(this.getRouteParams(), this.route.queryParams)
.pipe(take(1))
.subscribe(
([params, queryParams]: [Params, Params]) => {
this.store.dispatch(new SetParametersAction(params));
this.store.dispatch(new SetQueryParametersAction(queryParams));
}
)
}
} }

View File

@@ -2,12 +2,13 @@ import { ListableObject } from '../../shared/object-collection/shared/listable-o
import { TypedObject } from '../cache/object-cache.reducer'; import { TypedObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { GenericConstructor } from './generic-constructor'; import { GenericConstructor } from './generic-constructor';
import { excludeFromEquals } from '../utilities/equals.decorators';
/** /**
* Class object representing a browse entry * Class object representing a browse entry
* This class is not normalized because browse entries do not have self links * This class is not normalized because browse entries do not have self links
*/ */
export class BrowseEntry implements ListableObject { export class BrowseEntry extends ListableObject implements TypedObject {
static type = new ResourceType('browseEntry'); static type = new ResourceType('browseEntry');
/** /**
@@ -28,6 +29,7 @@ export class BrowseEntry implements ListableObject {
/** /**
* The count of this browse entry * The count of this browse entry
*/ */
@excludeFromEquals
count: number; count: number;
/** /**

View File

@@ -11,25 +11,29 @@ import { hasNoValue, isUndefined } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { GenericConstructor } from './generic-constructor'; import { GenericConstructor } from './generic-constructor';
/** /**
* An abstract model class for a DSpaceObject. * An abstract model class for a DSpaceObject.
*/ */
export class DSpaceObject implements CacheableObject, ListableObject { export class DSpaceObject extends ListableObject implements CacheableObject {
/** /**
* A string representing the kind of DSpaceObject, e.g. community, item, … * A string representing the kind of DSpaceObject, e.g. community, item, …
*/ */
static type = new ResourceType('dspaceobject'); static type = new ResourceType('dspaceobject');
@excludeFromEquals
private _name: string; private _name: string;
@excludeFromEquals
self: string; self: string;
/** /**
* The human-readable identifier of this DSpaceObject * The human-readable identifier of this DSpaceObject
*/ */
@excludeFromEquals
id: string; id: string;
/** /**
@@ -37,6 +41,12 @@ export class DSpaceObject implements CacheableObject, ListableObject {
*/ */
uuid: string; uuid: string;
/**
* A string representing the kind of DSpaceObject, e.g. community, item, …
*/
@excludeFromEquals
type: ResourceType;
/** /**
* The name for this DSpaceObject * The name for this DSpaceObject
*/ */
@@ -54,6 +64,7 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/** /**
* All metadata of this DSpaceObject * All metadata of this DSpaceObject
*/ */
@excludeFromEquals
metadata: MetadataMap; metadata: MetadataMap;
/** /**
@@ -66,11 +77,13 @@ export class DSpaceObject implements CacheableObject, ListableObject {
/** /**
* An array of DSpaceObjects that are direct parents of this DSpaceObject * An array of DSpaceObjects that are direct parents of this DSpaceObject
*/ */
@excludeFromEquals
parents: Observable<RemoteData<DSpaceObject[]>>; parents: Observable<RemoteData<DSpaceObject[]>>;
/** /**
* The DSpaceObject that owns this DSpaceObject * The DSpaceObject that owns this DSpaceObject
*/ */
@excludeFromEquals
owner: Observable<RemoteData<DSpaceObject>>; owner: Observable<RemoteData<DSpaceObject>>;
/** /**

View File

@@ -12,6 +12,8 @@ export class ItemType implements CacheableObject {
*/ */
id: string; id: string;
label: string;
/** /**
* The link to the rest endpoint where this object can be found * The link to the rest endpoint where this object can be found
*/ */

View File

@@ -46,6 +46,16 @@ export class Relationship implements CacheableObject {
*/ */
rightPlace: number; rightPlace: number;
/**
* The name variant of the Item to the left side of this Relationship
*/
leftwardValue: string;
/**
* The name variant of the Item to the right side of this Relationship
*/
rightwardValue: string;
/** /**
* The type of Relationship * The type of Relationship
*/ */

View File

@@ -81,6 +81,9 @@ export interface MetadataValueFilter {
/** The value constraint. */ /** The value constraint. */
value?: string; value?: string;
/** The authority constraint. */
authority?: string;
/** Whether the value constraint should match without regard to case. */ /** Whether the value constraint should match without regard to case. */
ignoreCase?: boolean; ignoreCase?: boolean;

View File

@@ -8,8 +8,8 @@ import {
} from './metadata.models'; } from './metadata.models';
import { Metadata } from './metadata.utils'; import { Metadata } from './metadata.utils';
const mdValue = (value: string, language?: string): MetadataValue => { const mdValue = (value: string, language?: string, authority?: string): MetadataValue => {
return Object.assign(new MetadataValue(), { uuid: uuidv4(), value: value, language: isUndefined(language) ? null : language, place: 0, authority: undefined, confidence: undefined }); return Object.assign(new MetadataValue(), { uuid: uuidv4(), value: value, language: isUndefined(language) ? null : language, place: 0, authority: isUndefined(authority) ? null : authority, confidence: undefined });
}; };
const dcDescription = mdValue('Some description'); const dcDescription = mdValue('Some description');
@@ -184,6 +184,8 @@ describe('Metadata', () => {
testValueMatches(mdValue('a'), true, { language: null }); testValueMatches(mdValue('a'), true, { language: null });
testValueMatches(mdValue('a'), false, { language: 'en_US' }); testValueMatches(mdValue('a'), false, { language: 'en_US' });
testValueMatches(mdValue('a', 'en_US'), true, { language: 'en_US' }); testValueMatches(mdValue('a', 'en_US'), true, { language: 'en_US' });
testValueMatches(mdValue('a', undefined, '4321'), true, { authority: '4321' });
testValueMatches(mdValue('a', undefined, '4321'), false, { authority: '1234' });
}); });
describe('toViewModelList method', () => { describe('toViewModelList method', () => {

View File

@@ -127,6 +127,8 @@ export class Metadata {
return true; return true;
} else if (filter.language && filter.language !== mdValue.language) { } else if (filter.language && filter.language !== mdValue.language) {
return false; return false;
} else if (filter.authority && filter.authority !== mdValue.authority) {
return false;
} else if (filter.value) { } else if (filter.value) {
let fValue = filter.value; let fValue = filter.value;
let mValue = mdValue.value; let mValue = mdValue.value;

View File

@@ -9,8 +9,7 @@ import { RequestService } from '../data/request.service';
import { BrowseDefinition } from './browse-definition.model'; import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { PaginatedList } from '../data/paginated-list'; import { PaginatedList } from '../data/paginated-list';
import { SearchResult } from '../../+search-page/search-result.model'; import { SearchResult } from '../../shared/search/search-result.model';
import { Item } from './item.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
/** /**

View File

@@ -1,9 +1,11 @@
import { autoserialize, autoserializeAs } from 'cerialize'; import { autoserialize, autoserializeAs } from 'cerialize';
import { hasValue } from '../../shared/empty.util';
/** /**
* Represents the state of a paginated response * Represents the state of a paginated response
*/ */
export class PageInfo { export class PageInfo {
/** /**
* The number of elements on a page * The number of elements on a page
*/ */
@@ -42,4 +44,20 @@ export class PageInfo {
@autoserialize @autoserialize
self: string; self: string;
constructor(
options?: {
elementsPerPage: number,
totalElements: number,
totalPages: number,
currentPage: number
}
) {
if (hasValue(options)) {
this.elementsPerPage = options.elementsPerPage;
this.totalElements = options.totalElements;
this.totalPages = options.totalPages;
this.currentPage = options.currentPage;
}
}
} }

View File

@@ -1,9 +1,9 @@
import { SearchConfigurationService } from './search-configuration.service'; import { SearchConfigurationService } from './search-configuration.service';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchFilter } from '../search-filter.model'; import { SearchFilter } from '../../../shared/search/search-filter.model';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
describe('SearchConfigurationService', () => { describe('SearchConfigurationService', () => {
@@ -30,14 +30,10 @@ describe('SearchConfigurationService', () => {
getRouteParameterValue: observableOf('') getRouteParameterValue: observableOf('')
}); });
const fixedFilterService = jasmine.createSpyObj('SearchFixedFilterService', {
getQueryByFilterName: observableOf(''),
});
const activatedRoute: any = new ActivatedRouteStub(); const activatedRoute: any = new ActivatedRouteStub();
beforeEach(() => { beforeEach(() => {
service = new SearchConfigurationService(routeService, fixedFilterService, activatedRoute); service = new SearchConfigurationService(routeService, activatedRoute);
}); });
describe('when the scope is called', () => { describe('when the scope is called', () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -1,27 +1,19 @@
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { import { BehaviorSubject, combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs';
BehaviorSubject, import { filter, map, startWith, switchMap } from 'rxjs/operators';
combineLatest as observableCombineLatest, import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
merge as observableMerge, import { SearchOptions } from '../../../shared/search/search-options.model';
Observable, import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
of as observableOf, import { SearchFilter } from '../../../shared/search/search-filter.model';
Subscription import { RemoteData } from '../../data/remote-data';
} from 'rxjs'; import { DSpaceObjectType } from '../dspace-object-type.model';
import { filter, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { RouteService } from '../../services/route.service';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { getSucceededRemoteData } from '../operators';
import { SearchOptions } from '../search-options.model'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
import { RouteService } from '../../core/services/route.service';
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { RemoteData } from '../../core/data/remote-data';
import { getSucceededRemoteData } from '../../core/shared/operators';
import { SearchFilter } from '../search-filter.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
import { SearchFixedFilterService } from '../search-filters/search-filter/search-fixed-filter.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
/** /**
* Service that performs all actions that have to do with the current search configuration * Service that performs all actions that have to do with the current search configuration
@@ -80,11 +72,9 @@ export class SearchConfigurationService implements OnDestroy {
/** /**
* Initialize the search options * Initialize the search options
* @param {RouteService} routeService * @param {RouteService} routeService
* @param {SearchFixedFilterService} fixedFilterService
* @param {ActivatedRoute} route * @param {ActivatedRoute} route
*/ */
constructor(protected routeService: RouteService, constructor(protected routeService: RouteService,
protected fixedFilterService: SearchFixedFilterService,
protected route: ActivatedRoute) { protected route: ActivatedRoute) {
this.initDefaults(); this.initDefaults();
@@ -96,7 +86,7 @@ export class SearchConfigurationService implements OnDestroy {
protected initDefaults() { protected initDefaults() {
this.defaults this.defaults
.pipe(getSucceededRemoteData()) .pipe(getSucceededRemoteData())
.subscribe((defRD) => { .subscribe((defRD: RemoteData<PaginatedSearchOptions>) => {
const defs = defRD.payload; const defs = defRD.payload;
this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs); this.paginatedSearchOptions = new BehaviorSubject<PaginatedSearchOptions>(defs);
this.searchOptions = new BehaviorSubject<SearchOptions>(defs); this.searchOptions = new BehaviorSubject<SearchOptions>(defs);
@@ -205,6 +195,13 @@ export class SearchConfigurationService implements OnDestroy {
})); }));
} }
/**
* @returns {Observable<string>} Emits the current fixed filter as a string
*/
getCurrentFixedFilter(): Observable<string> {
return this.routeService.getRouteParameterValue('fixedFilterQuery');
}
/** /**
* @returns {Observable<Params>} Emits the current active filters with their values as they are displayed in the frontend URL * @returns {Observable<Params>} Emits the current active filters with their values as they are displayed in the frontend URL
*/ */
@@ -224,9 +221,10 @@ export class SearchConfigurationService implements OnDestroy {
this.getQueryPart(defaults.query), this.getQueryPart(defaults.query),
this.getDSOTypePart(), this.getDSOTypePart(),
this.getFiltersPart(), this.getFiltersPart(),
this.getFixedFilterPart()
).subscribe((update) => { ).subscribe((update) => {
const currentValue: SearchOptions = this.searchOptions.getValue(); const currentValue: SearchOptions = this.searchOptions.getValue();
const updatedValue: SearchOptions = Object.assign(new SearchOptions({}), currentValue, update); const updatedValue: SearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
this.searchOptions.next(updatedValue); this.searchOptions.next(updatedValue);
}); });
} }
@@ -245,6 +243,7 @@ export class SearchConfigurationService implements OnDestroy {
this.getQueryPart(defaults.query), this.getQueryPart(defaults.query),
this.getDSOTypePart(), this.getDSOTypePart(),
this.getFiltersPart(), this.getFiltersPart(),
this.getFixedFilterPart()
).subscribe((update) => { ).subscribe((update) => {
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue(); const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update); const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
@@ -341,4 +340,16 @@ export class SearchConfigurationService implements OnDestroy {
return { filters } return { filters }
})); }));
} }
/**
* @returns {Observable<string>} Emits the current fixed filter as a partial SearchOptions object
*/
private getFixedFilterPart(): Observable<any> {
return this.getCurrentFixedFilter().pipe(
isNotEmptyOperator(),
map((fixedFilter) => {
return { fixedFilter }
}),
);
}
} }

View File

@@ -8,14 +8,13 @@ import {
SearchFilterInitializeAction, SearchFilterInitializeAction,
SearchFilterResetPageAction, SearchFilterResetPageAction,
SearchFilterToggleAction SearchFilterToggleAction
} from './search-filter.actions'; } from '../../../shared/search/search-filters/search-filter/search-filter.actions';
import { SearchFiltersState } from './search-filter.reducer'; import { SearchFiltersState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
import { FilterType } from '../../search-service/filter-type.model'; import { FilterType } from '../../../shared/search/filter-type.model';
import { SearchFixedFilterService } from './search-fixed-filter.service';
import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub'; import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
describe('SearchFilterService', () => { describe('SearchFilterService', () => {
let service: SearchFilterService; let service: SearchFilterService;
@@ -28,7 +27,6 @@ describe('SearchFilterService', () => {
pageSize: 2 pageSize: 2
}); });
const mockFixedFilterService: SearchFixedFilterService = {} as SearchFixedFilterService
const value1 = 'random value'; const value1 = 'random value';
// const value2 = 'another value'; // const value2 = 'another value';
const store: Store<SearchFiltersState> = jasmine.createSpyObj('store', { const store: Store<SearchFiltersState> = jasmine.createSpyObj('store', {
@@ -66,7 +64,7 @@ describe('SearchFilterService', () => {
}; };
beforeEach(() => { beforeEach(() => {
service = new SearchFilterService(store, routeServiceStub, mockFixedFilterService); service = new SearchFilterService(store, routeServiceStub);
}); });
describe('when the initializeFilter method is triggered', () => { describe('when the initializeFilter method is triggered', () => {

View File

@@ -1,7 +1,7 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators'; import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators';
import { Injectable, InjectionToken } from '@angular/core'; import { Injectable, InjectionToken } from '@angular/core';
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; import { SearchFiltersState, SearchFilterState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { import {
SearchFilterCollapseAction, SearchFilterCollapseAction,
@@ -11,13 +11,12 @@ import {
SearchFilterInitializeAction, SearchFilterInitializeAction,
SearchFilterResetPageAction, SearchFilterResetPageAction,
SearchFilterToggleAction SearchFilterToggleAction
} from './search-filter.actions'; } from '../../../shared/search/search-filters/search-filter/search-filter.actions';
import { hasValue, isNotEmpty, } from '../../../shared/empty.util'; import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
import { RouteService } from '../../../core/services/route.service'; import { RouteService } from '../../../core/services/route.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SearchFixedFilterService } from './search-fixed-filter.service';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
@@ -32,8 +31,7 @@ export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boole
export class SearchFilterService { export class SearchFilterService {
constructor(private store: Store<SearchFiltersState>, constructor(private store: Store<SearchFiltersState>,
private routeService: RouteService, private routeService: RouteService) {
private fixedFilterService: SearchFixedFilterService) {
} }
/** /**

View File

@@ -5,27 +5,27 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SearchService } from './search.service'; import { SearchService } from './search.service';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
import { Router, UrlTree } from '@angular/router'; import { Router, UrlTree } from '@angular/router';
import { RequestService } from '../../core/data/request.service'; import { RequestService } from '../../data/request.service';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
import { RouterStub } from '../../shared/testing/router-stub'; import { RouterStub } from '../../../shared/testing/router-stub';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { HALEndpointService } from '../hal-endpoint.service';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { PaginatedSearchOptions } from '../paginated-search-options.model'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../data/remote-data';
import { RequestEntry } from '../../core/data/request.reducer'; import { RequestEntry } from '../../data/request.reducer';
import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { getMockRequestService } from '../../../shared/mocks/mock-request.service';
import { FacetConfigSuccessResponse, SearchSuccessResponse } from '../../core/cache/response.models'; import { FacetConfigSuccessResponse, SearchSuccessResponse } from '../../cache/response.models';
import { SearchQueryResponse } from './search-query-response.model'; import { SearchQueryResponse } from '../../../shared/search/search-query-response.model';
import { SearchFilterConfig } from './search-filter-config.model'; import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../data/community-data.service';
import { ViewMode } from '../../core/shared/view-mode.model'; import { ViewMode } from '../view-mode.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { RouteService } from '../../core/services/route.service'; import { RouteService } from '../../services/route.service';
import { routeServiceStub } from '../../shared/testing/route-service-stub'; import { routeServiceStub } from '../../../shared/testing/route-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
@Component({ template: '' }) @Component({ template: '' })
class DummyComponent { class DummyComponent {

View File

@@ -1,48 +1,39 @@
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { NavigationExtras, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; import { NavigationExtras, Router } from '@angular/router';
import { first, map, switchMap, take, tap } from 'rxjs/operators'; import { first, map, switchMap, tap } from 'rxjs/operators';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { FacetConfigSuccessResponse, FacetValueSuccessResponse, SearchSuccessResponse } from '../../cache/response.models';
import { import { PaginatedList } from '../../data/paginated-list';
FacetConfigSuccessResponse, import { ResponseParsingService } from '../../data/parsing.service';
FacetValueSuccessResponse, import { RemoteData } from '../../data/remote-data';
SearchSuccessResponse import { GetRequest, RestRequest } from '../../data/request.models';
} from '../../core/cache/response.models'; import { RequestService } from '../../data/request.service';
import { PaginatedList } from '../../core/data/paginated-list'; import { DSpaceObject } from '../dspace-object.model';
import { ResponseParsingService } from '../../core/data/parsing.service'; import { GenericConstructor } from '../generic-constructor';
import { RemoteData } from '../../core/data/remote-data'; import { HALEndpointService } from '../hal-endpoint.service';
import { GetRequest, RestRequest } from '../../core/data/request.models'; import { URLCombiner } from '../../url-combiner/url-combiner';
import { RequestService } from '../../core/data/request.service'; import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../../shared/empty.util';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { NormalizedSearchResult } from '../../../shared/search/normalized-search-result.model';
import { GenericConstructor } from '../../core/shared/generic-constructor'; import { SearchOptions } from '../../../shared/search/search-options.model';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { SearchResult } from '../../../shared/search/search-result.model';
import { import { FacetValue } from '../../../shared/search/facet-value.model';
configureRequest, import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
filterSuccessfulResponses, import { SearchResponseParsingService } from '../../data/search-response-parsing.service';
getResponseFromEntry, import { SearchQueryResponse } from '../../../shared/search/search-query-response.model';
getSucceededRemoteData import { PageInfo } from '../page-info.model';
} from '../../core/shared/operators'; import { getSearchResultFor } from '../../../shared/search/search-result-element-decorator';
import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { FacetValueResponseParsingService } from '../../data/facet-value-response-parsing.service';
import { NormalizedSearchResult } from '../normalized-search-result.model'; import { FacetConfigResponseParsingService } from '../../data/facet-config-response-parsing.service';
import { SearchOptions } from '../search-options.model'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchResult } from '../search-result.model'; import { Community } from '../community.model';
import { FacetValue } from './facet-value.model'; import { CommunityDataService } from '../../data/community-data.service';
import { SearchFilterConfig } from './search-filter-config.model'; import { ViewMode } from '../view-mode.model';
import { SearchResponseParsingService } from '../../core/data/search-response-parsing.service'; import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
import { SearchQueryResponse } from './search-query-response.model'; import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
import { PageInfo } from '../../core/shared/page-info.model'; import { configureRequest, filterSuccessfulResponses, getResponseFromEntry, getSucceededRemoteData } from '../operators';
import { getSearchResultFor } from './search-result-element-decorator'; import { RouteService } from '../../services/route.service';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { RequestEntry } from '../../data/request.reducer';
import { FacetValueResponseParsingService } from '../../core/data/facet-value-response-parsing.service';
import { FacetConfigResponseParsingService } from '../../core/data/facet-config-response-parsing.service';
import { PaginatedSearchOptions } from '../paginated-search-options.model';
import { Community } from '../../core/shared/community.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { ViewMode } from '../../core/shared/view-mode.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { RouteService } from '../../core/services/route.service';
import { RequestEntry } from '../../core/data/request.reducer';
/** /**
* Service that performs all general actions that have to do with the search page * Service that performs all general actions that have to do with the search page
@@ -127,7 +118,7 @@ export class SearchService implements OnDestroy {
* @returns {Observable<RequestEntry>} Emits an observable with the request entries * @returns {Observable<RequestEntry>} Emits an observable with the request entries
*/ */
searchEntries(searchOptions?: PaginatedSearchOptions, responseMsToLive?:number) searchEntries(searchOptions?: PaginatedSearchOptions, responseMsToLive?:number)
:Observable<{searchOptions:PaginatedSearchOptions, requestEntry:RequestEntry}> { :Observable<{searchOptions: PaginatedSearchOptions, requestEntry: RequestEntry}> {
const hrefObs = this.getEndpoint(searchOptions); const hrefObs = this.getEndpoint(searchOptions);

Some files were not shown because too many files have changed in this diff Show More