mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'master' into performance-optimizations
Conflicts: src/app/core/data/request.service.spec.ts src/app/core/data/request.service.ts
This commit is contained in:
@@ -668,5 +668,33 @@
|
|||||||
},
|
},
|
||||||
"chips": {
|
"chips": {
|
||||||
"remove": "Remove chip"
|
"remove": "Remove chip"
|
||||||
|
},
|
||||||
|
"dso-selector": {
|
||||||
|
"create": {
|
||||||
|
"community": {
|
||||||
|
"head": "New community",
|
||||||
|
"sub-level": "Create a new community in",
|
||||||
|
"top-level": "Create a new top-level community"
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"head": "New collection"
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"head": "New item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"community": {
|
||||||
|
"head": "Edit community"
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"head": "Edit collection"
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"head": "Edit item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placeholder": "Search for a {{ type }}",
|
||||||
|
"no-results": "No {{ type }} found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import { AuthService } from '../../core/auth/auth.service';
|
|||||||
|
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
describe('AdminSidebarComponent', () => {
|
describe('AdminSidebarComponent', () => {
|
||||||
let comp: AdminSidebarComponent;
|
let comp: AdminSidebarComponent;
|
||||||
@@ -26,7 +27,12 @@ describe('AdminSidebarComponent', () => {
|
|||||||
{ provide: Injector, useValue: {} },
|
{ provide: Injector, useValue: {} },
|
||||||
{ provide: MenuService, useValue: menuService },
|
{ provide: MenuService, useValue: menuService },
|
||||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||||
{ provide: AuthService, useClass: AuthServiceStub }
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{
|
||||||
|
provide: NgbModal, useValue: {
|
||||||
|
open: () => {/*comment*/}
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(AdminSidebarComponent, {
|
}).overrideComponent(AdminSidebarComponent, {
|
||||||
@@ -96,7 +102,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(menuService, 'toggleMenu');
|
spyOn(menuService, 'toggleMenu');
|
||||||
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('a.shortcut-icon'));
|
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('a.shortcut-icon'));
|
||||||
sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
|
sidebarToggler.triggerEventHandler('click', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call toggleMenu on the menuService', () => {
|
it('should call toggleMenu on the menuService', () => {
|
||||||
@@ -108,7 +117,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(menuService, 'toggleMenu');
|
spyOn(menuService, 'toggleMenu');
|
||||||
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('.sidebar-collapsible')).query(By.css('a'));
|
const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('.sidebar-collapsible')).query(By.css('a'));
|
||||||
sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
|
sidebarToggler.triggerEventHandler('click', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call toggleMenu on the menuService', () => {
|
it('should call toggleMenu on the menuService', () => {
|
||||||
@@ -120,7 +132,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
it('should call expandPreview on the menuService after 100ms', fakeAsync(() => {
|
it('should call expandPreview on the menuService after 100ms', fakeAsync(() => {
|
||||||
spyOn(menuService, 'expandMenuPreview');
|
spyOn(menuService, 'expandMenuPreview');
|
||||||
const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
|
const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
|
||||||
sidebarToggler.triggerEventHandler('mouseenter', {preventDefault: () => {/**/}});
|
sidebarToggler.triggerEventHandler('mouseenter', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
tick(99);
|
tick(99);
|
||||||
expect(menuService.expandMenuPreview).not.toHaveBeenCalled();
|
expect(menuService.expandMenuPreview).not.toHaveBeenCalled();
|
||||||
tick(1);
|
tick(1);
|
||||||
@@ -132,7 +147,10 @@ describe('AdminSidebarComponent', () => {
|
|||||||
it('should call collapseMenuPreview on the menuService after 400ms', fakeAsync(() => {
|
it('should call collapseMenuPreview on the menuService after 400ms', fakeAsync(() => {
|
||||||
spyOn(menuService, 'collapseMenuPreview');
|
spyOn(menuService, 'collapseMenuPreview');
|
||||||
const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
|
const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
|
||||||
sidebarToggler.triggerEventHandler('mouseleave', {preventDefault: () => {/**/}});
|
sidebarToggler.triggerEventHandler('mouseleave', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
tick(399);
|
tick(399);
|
||||||
expect(menuService.collapseMenuPreview).not.toHaveBeenCalled();
|
expect(menuService.collapseMenuPreview).not.toHaveBeenCalled();
|
||||||
tick(1);
|
tick(1);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component, Injector, OnInit } from '@angular/core';
|
import { Component, Injector, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { slide, slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
||||||
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';
|
||||||
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
|
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
|
||||||
@@ -10,6 +10,14 @@ import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model
|
|||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { first, map } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { combineLatest as combineLatestObservable } from 'rxjs';
|
import { combineLatest as combineLatestObservable } from 'rxjs';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
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 { 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 { 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 { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the admin sidebar
|
* Component representing the admin sidebar
|
||||||
@@ -52,7 +60,8 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
constructor(protected menuService: MenuService,
|
constructor(protected menuService: MenuService,
|
||||||
protected injector: Injector,
|
protected injector: Injector,
|
||||||
private variableService: CSSVariableService,
|
private variableService: CSSVariableService,
|
||||||
private authService: AuthService
|
private authService: AuthService,
|
||||||
|
private modalService: NgbModal
|
||||||
) {
|
) {
|
||||||
super(menuService, injector);
|
super(menuService, injector);
|
||||||
}
|
}
|
||||||
@@ -104,10 +113,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.new_community',
|
text: 'menu.section.new_community',
|
||||||
link: '/communities/submission'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(CreateCommunityParentSelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_collection',
|
id: 'new_collection',
|
||||||
@@ -115,10 +126,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.new_collection',
|
text: 'menu.section.new_collection',
|
||||||
link: '/collections/submission'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(CreateCollectionParentSelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_item',
|
id: 'new_item',
|
||||||
@@ -126,10 +139,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.new_item',
|
text: 'menu.section.new_item',
|
||||||
link: '/items/submission'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(CreateItemParentSelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_item_version',
|
id: 'new_item_version',
|
||||||
@@ -161,10 +176,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.edit_community',
|
text: 'menu.section.edit_community',
|
||||||
link: '#'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(EditCommunitySelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'edit_collection',
|
id: 'edit_collection',
|
||||||
@@ -172,10 +189,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.edit_collection',
|
text: 'menu.section.edit_collection',
|
||||||
link: '#'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(EditCollectionSelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'edit_item',
|
id: 'edit_item',
|
||||||
@@ -183,10 +202,12 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.ONCLICK,
|
||||||
text: 'menu.section.edit_item',
|
text: 'menu.section.edit_item',
|
||||||
link: '#'
|
function: () => {
|
||||||
} as LinkMenuItemModel,
|
this.modalService.open(EditItemSelectorComponent);
|
||||||
|
}
|
||||||
|
} as OnClickMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Import */
|
/* Import */
|
||||||
@@ -223,7 +244,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
link: '#'
|
link: '#'
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Export */
|
/* Export */
|
||||||
{
|
{
|
||||||
id: 'export',
|
id: 'export',
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
|
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
|
||||||
import { AdminRoutingModule } from './admin-routing.module';
|
import { AdminRoutingModule } from './admin-routing.module';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
AdminRegistriesModule,
|
AdminRegistriesModule,
|
||||||
AdminRoutingModule,
|
AdminRoutingModule,
|
||||||
]
|
SharedModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AdminModule {
|
export class AdminModule {
|
||||||
|
|
||||||
|
@@ -8,17 +8,36 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
|||||||
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
||||||
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||||
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
import { getCollectionModulePath } from '../app-routing.module';
|
||||||
|
|
||||||
|
export const COLLECTION_PARENT_PARAMETER = 'parent';
|
||||||
|
|
||||||
|
export function getCollectionPageRoute(collectionId: string) {
|
||||||
|
return new URLCombiner(getCollectionModulePath(), collectionId).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCollectionEditPath(id: string) {
|
||||||
|
return new URLCombiner(getCollectionModulePath(), COLLECTION_EDIT_PATH.replace(/:id/, id)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCollectionCreatePath() {
|
||||||
|
return new URLCombiner(getCollectionModulePath(), COLLECTION_CREATE_PATH).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLLECTION_CREATE_PATH = 'create';
|
||||||
|
const COLLECTION_EDIT_PATH = ':id/edit';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: 'create',
|
path: COLLECTION_CREATE_PATH,
|
||||||
component: CreateCollectionPageComponent,
|
component: CreateCollectionPageComponent,
|
||||||
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard]
|
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id/edit',
|
path: COLLECTION_EDIT_PATH,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: EditCollectionPageComponent,
|
component: EditCollectionPageComponent,
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
|
@@ -15,7 +15,6 @@ import { DeleteCollectionPageComponent } from './delete-collection-page/delete-c
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
SearchPageModule,
|
|
||||||
CollectionPageRoutingModule
|
CollectionPageRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -8,17 +8,36 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
|||||||
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
|
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
|
||||||
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
||||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||||
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
import { getCommunityModulePath } from '../app-routing.module';
|
||||||
|
|
||||||
|
export const COMMUNITY_PARENT_PARAMETER = 'parent';
|
||||||
|
|
||||||
|
export function getCommunityPageRoute(communityId: string) {
|
||||||
|
return new URLCombiner(getCommunityModulePath(), communityId).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommunityEditPath(id: string) {
|
||||||
|
return new URLCombiner(getCommunityModulePath(), COMMUNITY_EDIT_PATH.replace(/:id/, id)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommunityCreatePath() {
|
||||||
|
return new URLCombiner(getCommunityModulePath(), COMMUNITY_CREATE_PATH).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const COMMUNITY_CREATE_PATH = 'create';
|
||||||
|
const COMMUNITY_EDIT_PATH = ':id/edit';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: 'create',
|
path: COMMUNITY_CREATE_PATH,
|
||||||
component: CreateCommunityPageComponent,
|
component: CreateCommunityPageComponent,
|
||||||
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard]
|
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id/edit',
|
path: COMMUNITY_EDIT_PATH,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: EditCommunityPageComponent,
|
component: EditCommunityPageComponent,
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
|
@@ -11,7 +11,6 @@ import { CommunitySearchResultListElementComponent } from '../shared/object-list
|
|||||||
import { ItemSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component';
|
import { ItemSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component';
|
||||||
import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.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 { CollectionSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component';
|
||||||
import { SearchService } from './search-service/search.service';
|
|
||||||
import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component';
|
import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects';
|
import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects';
|
||||||
@@ -49,13 +48,9 @@ const effects = [
|
|||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
SearchSidebarComponent,
|
SearchSidebarComponent,
|
||||||
SearchSettingsComponent,
|
SearchSettingsComponent,
|
||||||
ItemSearchResultListElementComponent,
|
|
||||||
CollectionSearchResultListElementComponent,
|
|
||||||
CommunitySearchResultListElementComponent,
|
|
||||||
ItemSearchResultGridElementComponent,
|
ItemSearchResultGridElementComponent,
|
||||||
CollectionSearchResultGridElementComponent,
|
CollectionSearchResultGridElementComponent,
|
||||||
CommunitySearchResultGridElementComponent,
|
CommunitySearchResultGridElementComponent,
|
||||||
CommunitySearchResultListElementComponent,
|
|
||||||
SearchFiltersComponent,
|
SearchFiltersComponent,
|
||||||
SearchFilterComponent,
|
SearchFilterComponent,
|
||||||
SearchFacetFilterComponent,
|
SearchFacetFilterComponent,
|
||||||
@@ -71,7 +66,6 @@ const effects = [
|
|||||||
SearchFacetRangeOptionComponent
|
SearchFacetRangeOptionComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
|
||||||
SearchSidebarService,
|
SearchSidebarService,
|
||||||
SearchFilterService,
|
SearchFilterService,
|
||||||
SearchConfigurationService
|
SearchConfigurationService
|
||||||
|
@@ -23,7 +23,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import {
|
import {
|
||||||
configureRequest,
|
configureRequest, filterSuccessfulResponses,
|
||||||
getResponseFromEntry,
|
getResponseFromEntry,
|
||||||
getSucceededRemoteData
|
getSucceededRemoteData
|
||||||
} from '../../core/shared/operators';
|
} from '../../core/shared/operators';
|
||||||
@@ -104,7 +104,7 @@ export class SearchService implements OnDestroy {
|
|||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const sqrObs: Observable<SearchQueryResponse> = requestEntryObs.pipe(
|
const sqrObs: Observable<SearchQueryResponse> = requestEntryObs.pipe(
|
||||||
getResponseFromEntry(),
|
filterSuccessfulResponses(),
|
||||||
map((response: SearchSuccessResponse) => response.results)
|
map((response: SearchSuccessResponse) => response.results)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -8,13 +8,21 @@ const ITEM_MODULE_PATH = 'items';
|
|||||||
export function getItemModulePath() {
|
export function getItemModulePath() {
|
||||||
return `/${ITEM_MODULE_PATH}`;
|
return `/${ITEM_MODULE_PATH}`;
|
||||||
}
|
}
|
||||||
|
const COLLECTION_MODULE_PATH = 'collections';
|
||||||
|
export function getCollectionModulePath() {
|
||||||
|
return `/${COLLECTION_MODULE_PATH}`;
|
||||||
|
}
|
||||||
|
const COMMUNITY_MODULE_PATH = 'communities';
|
||||||
|
export function getCommunityModulePath() {
|
||||||
|
return `/${COMMUNITY_MODULE_PATH}`;
|
||||||
|
}
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forRoot([
|
RouterModule.forRoot([
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
||||||
{ path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
{ path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
||||||
{ path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
{ path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
|
||||||
{ path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
{ path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
||||||
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
||||||
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
|
||||||
|
@@ -5,8 +5,14 @@ import {
|
|||||||
race as observableRace
|
race as observableRace
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { distinctUntilChanged, flatMap, map, startWith, switchMap } from 'rxjs/operators';
|
import { distinctUntilChanged, flatMap, map, startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util';
|
import {
|
||||||
|
hasValue,
|
||||||
|
hasValueOperator,
|
||||||
|
isEmpty,
|
||||||
|
isNotEmpty,
|
||||||
|
isNotUndefined
|
||||||
|
} from '../../../shared/empty.util';
|
||||||
import { PaginatedList } from '../../data/paginated-list';
|
import { PaginatedList } from '../../data/paginated-list';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
import { RemoteDataError } from '../../data/remote-data-error';
|
import { RemoteDataError } from '../../data/remote-data-error';
|
||||||
@@ -50,13 +56,13 @@ export class RemoteDataBuildService {
|
|||||||
const payload$ =
|
const payload$ =
|
||||||
observableCombineLatest(
|
observableCombineLatest(
|
||||||
href$.pipe(
|
href$.pipe(
|
||||||
switchMap((href: string) => this.objectCache.getBySelfLink<T>(href)),
|
switchMap((href: string) => this.objectCache.getObjectBySelfLink<T>(href)),
|
||||||
startWith(undefined)),
|
startWith(undefined)),
|
||||||
requestEntry$.pipe(
|
requestEntry$.pipe(
|
||||||
getResourceLinksFromResponse(),
|
getResourceLinksFromResponse(),
|
||||||
switchMap((resourceSelfLinks: string[]) => {
|
switchMap((resourceSelfLinks: string[]) => {
|
||||||
if (isNotEmpty(resourceSelfLinks)) {
|
if (isNotEmpty(resourceSelfLinks)) {
|
||||||
return this.objectCache.getBySelfLink<T>(resourceSelfLinks[0]);
|
return this.objectCache.getObjectBySelfLink<T>(resourceSelfLinks[0]);
|
||||||
} else {
|
} else {
|
||||||
return observableOf(undefined);
|
return observableOf(undefined);
|
||||||
}
|
}
|
||||||
|
@@ -80,7 +80,7 @@ describe('ObjectCacheService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// due to the implementation of spyOn above, this subscribe will be synchronous
|
// due to the implementation of spyOn above, this subscribe will be synchronous
|
||||||
service.getBySelfLink(selfLink).pipe(first()).subscribe((o) => {
|
service.getObjectBySelfLink(selfLink).pipe(first()).subscribe((o) => {
|
||||||
expect(o.self).toBe(selfLink);
|
expect(o.self).toBe(selfLink);
|
||||||
// this only works if testObj is an instance of TestClass
|
// this only works if testObj is an instance of TestClass
|
||||||
expect(o instanceof NormalizedItem).toBeTruthy();
|
expect(o instanceof NormalizedItem).toBeTruthy();
|
||||||
@@ -96,7 +96,7 @@ describe('ObjectCacheService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let getObsHasFired = false;
|
let getObsHasFired = false;
|
||||||
const subscription = service.getBySelfLink(selfLink).subscribe((o) => getObsHasFired = true);
|
const subscription = service.getObjectBySelfLink(selfLink).subscribe((o) => getObsHasFired = true);
|
||||||
expect(getObsHasFired).toBe(false);
|
expect(getObsHasFired).toBe(false);
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
@@ -106,7 +106,7 @@ describe('ObjectCacheService', () => {
|
|||||||
it('should return an observable of the array of cached objects with the specified self link and type', () => {
|
it('should return an observable of the array of cached objects with the specified self link and type', () => {
|
||||||
const item = new NormalizedItem();
|
const item = new NormalizedItem();
|
||||||
item.self = selfLink;
|
item.self = selfLink;
|
||||||
spyOn(service, 'getBySelfLink').and.returnValue(observableOf(item));
|
spyOn(service, 'getObjectBySelfLink').and.returnValue(observableOf(item));
|
||||||
|
|
||||||
service.getList([selfLink, selfLink]).pipe(first()).subscribe((arr) => {
|
service.getList([selfLink, selfLink]).pipe(first()).subscribe((arr) => {
|
||||||
expect(arr[0].self).toBe(selfLink);
|
expect(arr[0].self).toBe(selfLink);
|
||||||
|
58
src/app/core/cache/object-cache.service.ts
vendored
58
src/app/core/cache/object-cache.service.ts
vendored
@@ -68,29 +68,29 @@ export class ObjectCacheService {
|
|||||||
/**
|
/**
|
||||||
* Get an observable of the object with the specified UUID
|
* Get an observable of the object with the specified UUID
|
||||||
*
|
*
|
||||||
* The type needs to be specified as well, in order to turn
|
|
||||||
* the cached plain javascript object in to an instance of
|
|
||||||
* a class.
|
|
||||||
*
|
|
||||||
* e.g. getByUUID('c96588c6-72d3-425d-9d47-fa896255a695', Item)
|
|
||||||
*
|
|
||||||
* @param uuid
|
* @param uuid
|
||||||
* The UUID of the object to get
|
* The UUID of the object to get
|
||||||
* @param type
|
* @return Observable<NormalizedObject<T>>
|
||||||
* The type of the object to get
|
* An observable of the requested object in normalized form
|
||||||
* @return Observable<T>
|
|
||||||
* An observable of the requested object
|
|
||||||
*/
|
*/
|
||||||
getByUUID<T extends CacheableObject>(uuid: string): Observable<NormalizedObject<T>> {
|
getObjectByUUID<T extends CacheableObject>(uuid: string): Observable<NormalizedObject<T>> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(selfLinkFromUuidSelector(uuid)),
|
select(selfLinkFromUuidSelector(uuid)),
|
||||||
mergeMap((selfLink: string) => this.getBySelfLink(selfLink)
|
mergeMap((selfLink: string) => this.getObjectBySelfLink(selfLink)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getBySelfLink<T extends CacheableObject>(selfLink: string): Observable<NormalizedObject<T>> {
|
/**
|
||||||
return this.getEntry(selfLink).pipe(
|
* Get an observable of the object with the specified selfLink
|
||||||
|
*
|
||||||
|
* @param selfLink
|
||||||
|
* The selfLink of the object to get
|
||||||
|
* @return Observable<NormalizedObject<T>>
|
||||||
|
* An observable of the requested object in normalized form
|
||||||
|
*/
|
||||||
|
getObjectBySelfLink<T extends CacheableObject>(selfLink: string): Observable<NormalizedObject<T>> {
|
||||||
|
return this.getBySelfLink(selfLink).pipe(
|
||||||
map((entry: ObjectCacheEntry) => {
|
map((entry: ObjectCacheEntry) => {
|
||||||
if (isNotEmpty(entry.patches)) {
|
if (isNotEmpty(entry.patches)) {
|
||||||
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
|
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
|
||||||
@@ -108,7 +108,15 @@ export class ObjectCacheService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEntry(selfLink: string): Observable<ObjectCacheEntry> {
|
/**
|
||||||
|
* Get an observable of the object cache entry with the specified selfLink
|
||||||
|
*
|
||||||
|
* @param selfLink
|
||||||
|
* The selfLink of the object to get
|
||||||
|
* @return Observable<ObjectCacheEntry>
|
||||||
|
* An observable of the requested object cache entry
|
||||||
|
*/
|
||||||
|
getBySelfLink(selfLink: string): Observable<ObjectCacheEntry> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(entryFromSelfLinkSelector(selfLink)),
|
select(entryFromSelfLinkSelector(selfLink)),
|
||||||
filter((entry) => this.isValid(entry)),
|
filter((entry) => this.isValid(entry)),
|
||||||
@@ -116,12 +124,28 @@ export class ObjectCacheService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an observable of the request's uuid with the specified selfLink
|
||||||
|
*
|
||||||
|
* @param selfLink
|
||||||
|
* The selfLink of the object to get
|
||||||
|
* @return Observable<string>
|
||||||
|
* An observable of the request's uuid
|
||||||
|
*/
|
||||||
getRequestUUIDBySelfLink(selfLink: string): Observable<string> {
|
getRequestUUIDBySelfLink(selfLink: string): Observable<string> {
|
||||||
return this.getEntry(selfLink).pipe(
|
return this.getBySelfLink(selfLink).pipe(
|
||||||
map((entry: ObjectCacheEntry) => entry.requestUUID),
|
map((entry: ObjectCacheEntry) => entry.requestUUID),
|
||||||
distinctUntilChanged());
|
distinctUntilChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an observable of the request's uuid with the specified uuid
|
||||||
|
*
|
||||||
|
* @param uuid
|
||||||
|
* The uuid of the object to get
|
||||||
|
* @return Observable<string>
|
||||||
|
* An observable of the request's uuid
|
||||||
|
*/
|
||||||
getRequestUUIDByObjectUUID(uuid: string): Observable<string> {
|
getRequestUUIDByObjectUUID(uuid: string): Observable<string> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(selfLinkFromUuidSelector(uuid)),
|
select(selfLinkFromUuidSelector(uuid)),
|
||||||
@@ -150,7 +174,7 @@ export class ObjectCacheService {
|
|||||||
*/
|
*/
|
||||||
getList<T extends CacheableObject>(selfLinks: string[]): Observable<Array<NormalizedObject<T>>> {
|
getList<T extends CacheableObject>(selfLinks: string[]): Observable<Array<NormalizedObject<T>>> {
|
||||||
return observableCombineLatest(
|
return observableCombineLatest(
|
||||||
selfLinks.map((selfLink: string) => this.getBySelfLink<T>(selfLink))
|
selfLinks.map((selfLink: string) => this.getObjectBySelfLink<T>(selfLink))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,7 +47,7 @@ describe('ServerSyncBufferEffects', () => {
|
|||||||
{ provide: RequestService, useValue: getMockRequestService() },
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
{
|
{
|
||||||
provide: ObjectCacheService, useValue: {
|
provide: ObjectCacheService, useValue: {
|
||||||
getBySelfLink: (link) => {
|
getObjectBySelfLink: (link) => {
|
||||||
const object = new DSpaceObject();
|
const object = new DSpaceObject();
|
||||||
object.self = link;
|
object.self = link;
|
||||||
return observableOf(object);
|
return observableOf(object);
|
||||||
|
@@ -96,7 +96,7 @@ export class ServerSyncBufferEffects {
|
|||||||
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
|
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
|
||||||
*/
|
*/
|
||||||
private applyPatch(href: string): Observable<Action> {
|
private applyPatch(href: string): Observable<Action> {
|
||||||
const patchObject = this.objectCache.getBySelfLink(href).pipe(take(1));
|
const patchObject = this.objectCache.getObjectBySelfLink(href).pipe(take(1));
|
||||||
|
|
||||||
return patchObject.pipe(
|
return patchObject.pipe(
|
||||||
map((object) => {
|
map((object) => {
|
||||||
|
@@ -69,6 +69,7 @@ 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';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -138,6 +139,7 @@ const PROVIDERS = [
|
|||||||
CSSVariableService,
|
CSSVariableService,
|
||||||
MenuService,
|
MenuService,
|
||||||
ObjectUpdatesService,
|
ObjectUpdatesService,
|
||||||
|
SearchService,
|
||||||
// register AuthInterceptor as HttpInterceptor
|
// register AuthInterceptor as HttpInterceptor
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
@@ -94,7 +94,7 @@ describe('ComColDataService', () => {
|
|||||||
|
|
||||||
function initMockObjectCacheService(): ObjectCacheService {
|
function initMockObjectCacheService(): ObjectCacheService {
|
||||||
return jasmine.createSpyObj('objectCache', {
|
return jasmine.createSpyObj('objectCache', {
|
||||||
getByUUID: cold('d-', {
|
getObjectByUUID: cold('d-', {
|
||||||
d: {
|
d: {
|
||||||
_links: {
|
_links: {
|
||||||
[LINK_NAME]: scopedEndpoint
|
[LINK_NAME]: scopedEndpoint
|
||||||
@@ -159,7 +159,7 @@ describe('ComColDataService', () => {
|
|||||||
it('should fetch the scope Community from the cache', () => {
|
it('should fetch the scope Community from the cache', () => {
|
||||||
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
|
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID);
|
expect(objectCache.getObjectByUUID).toHaveBeenCalledWith(scopeID);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the endpoint to fetch resources within the given scope', () => {
|
it('should return the endpoint to fetch resources within the given scope', () => {
|
||||||
|
@@ -49,7 +49,7 @@ export abstract class ComColDataService<T extends CacheableObject> extends DataS
|
|||||||
);
|
);
|
||||||
const successResponses = responses.pipe(
|
const successResponses = responses.pipe(
|
||||||
filter((response) => response.isSuccessful),
|
filter((response) => response.isSuccessful),
|
||||||
mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
|
mergeMap(() => this.objectCache.getObjectByUUID(options.scopeID)),
|
||||||
map((nc: NormalizedCommunity) => nc._links[linkPath]),
|
map((nc: NormalizedCommunity) => nc._links[linkPath]),
|
||||||
filter((href) => isNotEmpty(href))
|
filter((href) => isNotEmpty(href))
|
||||||
);
|
);
|
||||||
|
@@ -67,7 +67,7 @@ describe('DataService', () => {
|
|||||||
addPatch: () => {
|
addPatch: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
},
|
},
|
||||||
getBySelfLink: () => {
|
getObjectBySelfLink: () => {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
} as any;
|
} as any;
|
||||||
@@ -189,7 +189,7 @@ describe('DataService', () => {
|
|||||||
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
||||||
|
|
||||||
spyOn(service, 'findById').and.returnValues(observableOf(dso));
|
spyOn(service, 'findById').and.returnValues(observableOf(dso));
|
||||||
spyOn(objectCache, 'getBySelfLink').and.returnValues(observableOf(dso));
|
spyOn(objectCache, 'getObjectBySelfLink').and.returnValues(observableOf(dso));
|
||||||
spyOn(objectCache, 'addPatch');
|
spyOn(objectCache, 'addPatch');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -139,7 +139,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
* @param {DSpaceObject} object The given object
|
* @param {DSpaceObject} object The given object
|
||||||
*/
|
*/
|
||||||
update(object: T): Observable<RemoteData<T>> {
|
update(object: T): Observable<RemoteData<T>> {
|
||||||
const oldVersion$ = this.objectCache.getBySelfLink(object.self);
|
const oldVersion$ = this.objectCache.getObjectBySelfLink(object.self);
|
||||||
return oldVersion$.pipe(take(1), mergeMap((oldVersion: T) => {
|
return oldVersion$.pipe(take(1), mergeMap((oldVersion: T) => {
|
||||||
const operations = this.comparator.diff(oldVersion, object);
|
const operations = this.comparator.diff(oldVersion, object);
|
||||||
if (isNotEmpty(operations)) {
|
if (isNotEmpty(operations)) {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||||
|
|
||||||
export class PaginatedList<T> {
|
export class PaginatedList<T> {
|
||||||
|
|
||||||
@@ -22,6 +22,9 @@ export class PaginatedList<T> {
|
|||||||
if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) {
|
if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) {
|
||||||
return this.pageInfo.totalElements;
|
return this.pageInfo.totalElements;
|
||||||
}
|
}
|
||||||
|
if (hasNoValue(this.page)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return this.page.length;
|
return this.page.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ import { BrowseItemsResponseParsingService } from './browse-items-response-parsi
|
|||||||
import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
|
import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
|
||||||
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
||||||
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
|
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
|
||||||
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
@@ -146,11 +147,11 @@ export class FindAllRequest extends GetRequest {
|
|||||||
|
|
||||||
export class EndpointMapRequest extends GetRequest {
|
export class EndpointMapRequest extends GetRequest {
|
||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
uuid: string,
|
||||||
public href: string,
|
href: string,
|
||||||
public body?: any
|
body?: any
|
||||||
) {
|
) {
|
||||||
super(uuid, href, body);
|
super(uuid, new URLCombiner(href, '?endpointMap').toString(), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf, EMPTY } from 'rxjs';
|
||||||
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
|
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
|
||||||
import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service';
|
import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -7,6 +7,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { UUIDService } from '../shared/uuid.service';
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||||
import * as ngrx from '@ngrx/store';
|
import * as ngrx from '@ngrx/store';
|
||||||
|
import { ActionsSubject, Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
DeleteRequest,
|
DeleteRequest,
|
||||||
GetRequest,
|
GetRequest,
|
||||||
@@ -18,7 +19,6 @@ import {
|
|||||||
RestRequest
|
RestRequest
|
||||||
} from './request.models';
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { ActionsSubject, Store } from '@ngrx/store';
|
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
import { MockStore } from '../../shared/testing/mock-store';
|
import { MockStore } from '../../shared/testing/mock-store';
|
||||||
@@ -42,6 +42,7 @@ describe('RequestService', () => {
|
|||||||
const testHeadRequest = new HeadRequest(testUUID, testHref);
|
const testHeadRequest = new HeadRequest(testUUID, testHref);
|
||||||
const testPatchRequest = new PatchRequest(testUUID, testHref);
|
const testPatchRequest = new PatchRequest(testUUID, testHref);
|
||||||
let selectSpy;
|
let selectSpy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
scheduler = getTestScheduler();
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
@@ -323,6 +324,7 @@ describe('RequestService', () => {
|
|||||||
describe('in the ObjectCache', () => {
|
describe('in the ObjectCache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(objectCache.hasBySelfLink as any).and.returnValue(true);
|
(objectCache.hasBySelfLink as any).and.returnValue(true);
|
||||||
|
spyOn(serviceAsAny, 'hasByHref').and.returnValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', () => {
|
it('should return true', () => {
|
||||||
@@ -332,63 +334,16 @@ describe('RequestService', () => {
|
|||||||
expect(result).toEqual(expected);
|
expect(result).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('in the responseCache', () => {
|
describe('in the request cache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(serviceAsAny, 'isReusable').and.returnValue(observableOf(true));
|
(objectCache.hasBySelfLink as any).and.returnValue(false);
|
||||||
spyOn(serviceAsAny, 'getByHref').and.returnValue(observableOf(undefined));
|
spyOn(serviceAsAny, 'hasByHref').and.returnValue(true);
|
||||||
});
|
});
|
||||||
|
it('should return true', () => {
|
||||||
|
const result = serviceAsAny.isCachedOrPending(testGetRequest);
|
||||||
|
const expected = true;
|
||||||
|
|
||||||
describe('and it\'s a DSOSuccessResponse', () => {
|
expect(result).toEqual(expected);
|
||||||
beforeEach(() => {
|
|
||||||
(serviceAsAny.getByHref as any).and.returnValue(observableOf({
|
|
||||||
response: {
|
|
||||||
isSuccessful: true,
|
|
||||||
resourceSelfLinks: [
|
|
||||||
'https://rest.api/endpoint/selfLink1',
|
|
||||||
'https://rest.api/endpoint/selfLink2'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if all top level links in the response are cached in the object cache', () => {
|
|
||||||
(objectCache.hasBySelfLink as any).and.returnValues(false, true, true);
|
|
||||||
|
|
||||||
const result = serviceAsAny.isCachedOrPending(testGetRequest);
|
|
||||||
const expected = true;
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
it('should return false if not all top level links in the response are cached in the object cache', () => {
|
|
||||||
(objectCache.hasBySelfLink as any).and.returnValues(false, true, false);
|
|
||||||
spyOn(service, 'isPending').and.returnValue(false);
|
|
||||||
|
|
||||||
const result = serviceAsAny.isCachedOrPending(testGetRequest);
|
|
||||||
const expected = false;
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and it isn\'t a DSOSuccessResponse', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
(objectCache.hasBySelfLink as any).and.returnValue(false);
|
|
||||||
(service as any).isReusable.and.returnValue(observableOf(true));
|
|
||||||
(serviceAsAny.getByHref as any).and.returnValue(observableOf({
|
|
||||||
response: {
|
|
||||||
isSuccessful: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true', () => {
|
|
||||||
const result = serviceAsAny.isCachedOrPending(testGetRequest);
|
|
||||||
const expected = true;
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -462,104 +417,128 @@ describe('RequestService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isReusable', () => {
|
describe('isValid', () => {
|
||||||
describe('when the given UUID is has no value', () => {
|
describe('when the given entry has no value', () => {
|
||||||
let reusable;
|
let valid;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const uuid = undefined;
|
const entry = undefined;
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
valid = serviceAsAny.isValid(entry);
|
||||||
});
|
});
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(false));
|
expect(valid).toBe(false);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the given UUID has a value, but no cached entry is found', () => {
|
describe('when the given entry has a value, but the request is not completed', () => {
|
||||||
let reusable;
|
let valid;
|
||||||
|
const requestEntry = { completed: false };
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf(undefined));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
||||||
const uuid = 'a45bb291-1adb-40d9-b2fc-7ad9080607be';
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
|
||||||
});
|
});
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(false));
|
expect(valid).toBe(false);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the given UUID has a value, a cached entry is found, but it has no response', () => {
|
describe('when the given entry has a value, but the response is not successful', () => {
|
||||||
let reusable;
|
let valid;
|
||||||
|
const requestEntry = { completed: true, response: { isSuccessful: false } };
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf({ response: undefined }));
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
||||||
const uuid = '53c9b814-ad8b-4567-9bc1-d9bb6cfba6c8';
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
|
||||||
});
|
});
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(false));
|
expect(valid).toBe(false);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the given UUID has a value, a cached entry is found, but its response was not successful', () => {
|
describe('when the given UUID has a value, its response was successful, but the response is outdated', () => {
|
||||||
let reusable;
|
let valid;
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf({ response: { isSuccessful: false } }));
|
|
||||||
const uuid = '694c9b32-7b2e-4788-835b-ef3fc2252e6c';
|
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
|
||||||
});
|
|
||||||
it('return an observable emitting false', () => {
|
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(false));
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the given UUID has a value, a cached entry is found, its response was successful, but the response is outdated', () => {
|
|
||||||
let reusable;
|
|
||||||
const now = 100000;
|
const now = 100000;
|
||||||
const timeAdded = 99899;
|
const timeAdded = 99899;
|
||||||
const msToLive = 100;
|
const msToLive = 100;
|
||||||
|
const requestEntry = {
|
||||||
|
completed: true,
|
||||||
|
response: {
|
||||||
|
isSuccessful: true,
|
||||||
|
timeAdded: timeAdded
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
responseMsToLive: msToLive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf({
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
||||||
response: {
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
isSuccessful: true,
|
|
||||||
timeAdded: timeAdded
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
responseMsToLive: msToLive
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
const uuid = 'f9b85788-881c-4994-86b6-bae8dad024d2';
|
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('return an observable emitting false', () => {
|
it('return an observable emitting false', () => {
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(false));
|
expect(valid).toBe(false);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the given UUID has a value, a cached entry is found, its response was successful, and the response is not outdated', () => {
|
describe('when the given UUID has a value, a cached entry is found, its response was successful, and the response is not outdated', () => {
|
||||||
let reusable;
|
let valid;
|
||||||
const now = 100000;
|
const now = 100000;
|
||||||
const timeAdded = 99999;
|
const timeAdded = 99999;
|
||||||
const msToLive = 100;
|
const msToLive = 100;
|
||||||
|
|
||||||
|
const requestEntry = {
|
||||||
|
completed: true,
|
||||||
|
response: {
|
||||||
|
isSuccessful: true,
|
||||||
|
timeAdded: timeAdded
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
responseMsToLive: msToLive
|
||||||
|
}
|
||||||
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
spyOn(Date.prototype, 'getTime').and.returnValue(now);
|
||||||
spyOn(service, 'getByUUID').and.returnValue(observableOf({
|
spyOn(service, 'getByUUID').and.returnValue(observableOf(requestEntry));
|
||||||
response: {
|
valid = serviceAsAny.isValid(requestEntry);
|
||||||
isSuccessful: true,
|
|
||||||
timeAdded: timeAdded
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
responseMsToLive: msToLive
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
const uuid = 'f9b85788-881c-4994-86b6-bae8dad024d2';
|
|
||||||
reusable = serviceAsAny.isReusable(uuid);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('return an observable emitting true', () => {
|
it('return an observable emitting true', () => {
|
||||||
reusable.subscribe((isReusable) => expect(isReusable).toBe(true));
|
expect(valid).toBe(true);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
|
|
||||||
|
describe('hasByHref', () => {
|
||||||
|
describe('when nothing is returned by getByHref', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(EMPTY);
|
||||||
|
});
|
||||||
|
it('hasByHref should return false', () => {
|
||||||
|
const result = service.hasByHref('');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when isValid returns false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(observableOf(undefined));
|
||||||
|
spyOn(service as any, 'isValid').and.returnValue(false);
|
||||||
|
});
|
||||||
|
it('hasByHref should return false', () => {
|
||||||
|
const result = service.hasByHref('');
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when isValid returns true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(observableOf(undefined));
|
||||||
|
spyOn(service as any, 'isValid').and.returnValue(true);
|
||||||
|
});
|
||||||
|
it('hasByHref should return true', () => {
|
||||||
|
const result = service.hasByHref('');
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,38 +1,31 @@
|
|||||||
import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
|
import { Observable, race as observableRace } from 'rxjs';
|
||||||
import {
|
import { filter, mergeMap, take } from 'rxjs/operators';
|
||||||
filter,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
import { race as observableRace } from 'rxjs';
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { DSOSuccessResponse, RestResponse } from '../cache/response.models';
|
import { coreSelector, CoreState } from '../core.reducers';
|
||||||
import { CoreState } from '../core.reducers';
|
import { IndexName, IndexState, MetaIndexState } from '../index/index.reducer';
|
||||||
import { coreSelector } from '../core.selectors';
|
import { pathSelector } from '../shared/selectors';
|
||||||
import {
|
import {
|
||||||
IndexName, IndexState,
|
originalRequestUUIDFromRequestUUIDSelector,
|
||||||
MetaIndexState
|
requestIndexSelector,
|
||||||
} from '../index/index.reducer';
|
|
||||||
import {
|
|
||||||
originalRequestUUIDFromRequestUUIDSelector, requestIndexSelector,
|
|
||||||
uuidFromHrefSelector
|
uuidFromHrefSelector
|
||||||
} from '../index/index.selectors';
|
} from '../index/index.selectors';
|
||||||
import { UUIDService } from '../shared/uuid.service';
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions';
|
import {
|
||||||
|
RequestConfigureAction,
|
||||||
|
RequestExecuteAction,
|
||||||
|
RequestRemoveAction
|
||||||
|
} from './request.actions';
|
||||||
import { GetRequest, RestRequest } from './request.models';
|
import { GetRequest, RestRequest } from './request.models';
|
||||||
|
|
||||||
import { RequestEntry, RequestState } from './request.reducer';
|
import { RequestEntry, RequestState } from './request.reducer';
|
||||||
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
|
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
import { getResponseFromEntry } from '../shared/operators';
|
|
||||||
import { AddToIndexAction, RemoveFromIndexBySubstringAction } from '../index/index.actions';
|
import { AddToIndexAction, RemoveFromIndexBySubstringAction } from '../index/index.actions';
|
||||||
|
|
||||||
const requestCacheSelector = createSelector(
|
const requestCacheSelector = createSelector(
|
||||||
@@ -88,6 +81,9 @@ export class RequestService {
|
|||||||
return `client/${this.uuidService.generate()}`;
|
return `client/${this.uuidService.generate()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a request is currently pending
|
||||||
|
*/
|
||||||
isPending(request: GetRequest): boolean {
|
isPending(request: GetRequest): boolean {
|
||||||
// first check requests that haven't made it to the store yet
|
// first check requests that haven't made it to the store yet
|
||||||
if (this.requestsOnTheirWayToTheStore.includes(request.href)) {
|
if (this.requestsOnTheirWayToTheStore.includes(request.href)) {
|
||||||
@@ -101,10 +97,12 @@ export class RequestService {
|
|||||||
.subscribe((re: RequestEntry) => {
|
.subscribe((re: RequestEntry) => {
|
||||||
isPending = (hasValue(re) && !re.completed)
|
isPending = (hasValue(re) && !re.completed)
|
||||||
});
|
});
|
||||||
|
|
||||||
return isPending;
|
return isPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a RequestEntry based on their uuid
|
||||||
|
*/
|
||||||
getByUUID(uuid: string): Observable<RequestEntry> {
|
getByUUID(uuid: string): Observable<RequestEntry> {
|
||||||
return observableRace(
|
return observableRace(
|
||||||
this.store.pipe(select(entryFromUUIDSelector(uuid))),
|
this.store.pipe(select(entryFromUUIDSelector(uuid))),
|
||||||
@@ -117,6 +115,9 @@ export class RequestService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a RequestEntry based on their href
|
||||||
|
*/
|
||||||
getByHref(href: string): Observable<RequestEntry> {
|
getByHref(href: string): Observable<RequestEntry> {
|
||||||
return this.store.pipe(
|
return this.store.pipe(
|
||||||
select(uuidFromHrefSelector(href)),
|
select(uuidFromHrefSelector(href)),
|
||||||
@@ -180,31 +181,11 @@ export class RequestService {
|
|||||||
* @param {GetRequest} request The request to check
|
* @param {GetRequest} request The request to check
|
||||||
* @returns {boolean} True if the request is cached or still pending
|
* @returns {boolean} True if the request is cached or still pending
|
||||||
*/
|
*/
|
||||||
private isCachedOrPending(request: GetRequest) {
|
private isCachedOrPending(request: GetRequest): boolean {
|
||||||
let isCached = this.objectCache.hasBySelfLink(request.href);
|
const inReqCache = this.hasByHref(request.href);
|
||||||
if (isCached) {
|
const inObjCache = this.objectCache.hasBySelfLink(request.href);
|
||||||
const responses: Observable<RestResponse> = this.isReusable(request.uuid).pipe(
|
const isCached = inReqCache || inObjCache;
|
||||||
filter((reusable: boolean) => reusable),
|
|
||||||
switchMap(() => {
|
|
||||||
return this.getByHref(request.href).pipe(
|
|
||||||
getResponseFromEntry(),
|
|
||||||
take(1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error.
|
|
||||||
const dsoSuccessResponses = responses.pipe(
|
|
||||||
filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)),
|
|
||||||
map((response: DSOSuccessResponse) => response.resourceSelfLinks),
|
|
||||||
map((resourceSelfLinks: string[]) => resourceSelfLinks
|
|
||||||
.every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
|
|
||||||
));
|
|
||||||
|
|
||||||
const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true));
|
|
||||||
|
|
||||||
observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c);
|
|
||||||
}
|
|
||||||
const isPending = this.isPending(request);
|
const isPending = this.isPending(request);
|
||||||
return isCached || isPending;
|
return isCached || isPending;
|
||||||
}
|
}
|
||||||
@@ -227,7 +208,7 @@ export class RequestService {
|
|||||||
*/
|
*/
|
||||||
private trackRequestsOnTheirWayToTheStore(request: GetRequest) {
|
private trackRequestsOnTheirWayToTheStore(request: GetRequest) {
|
||||||
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href];
|
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href];
|
||||||
this.store.pipe(select(entryFromUUIDSelector(request.href)),
|
this.getByHref(request.href).pipe(
|
||||||
filter((re: RequestEntry) => hasValue(re)),
|
filter((re: RequestEntry) => hasValue(re)),
|
||||||
take(1)
|
take(1)
|
||||||
).subscribe((re: RequestEntry) => {
|
).subscribe((re: RequestEntry) => {
|
||||||
@@ -244,31 +225,39 @@ export class RequestService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a Response should still be cached
|
* Check whether a cached response should still be valid
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param entry
|
||||||
* the uuid of the entry to check
|
* the entry to check
|
||||||
* @return boolean
|
* @return boolean
|
||||||
* false if the uuid has no value, no entry could be found, the response was nog successful or its time to
|
* false if the uuid has no value, the response was not successful or its time to
|
||||||
* live has exceeded, true otherwise
|
* live was exceeded, true otherwise
|
||||||
*/
|
*/
|
||||||
private isReusable(uuid: string): Observable<boolean> {
|
private isValid(entry: RequestEntry): boolean {
|
||||||
if (hasNoValue(uuid)) {
|
if (hasValue(entry) && entry.completed && entry.response.isSuccessful) {
|
||||||
return observableOf(false);
|
const timeOutdated = entry.response.timeAdded + entry.request.responseMsToLive;
|
||||||
|
const isOutDated = new Date().getTime() > timeOutdated;
|
||||||
|
return !isOutDated;
|
||||||
} else {
|
} else {
|
||||||
const requestEntry$ = this.getByUUID(uuid);
|
return false;
|
||||||
return requestEntry$.pipe(
|
|
||||||
filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)),
|
|
||||||
map((entry: RequestEntry) => {
|
|
||||||
if (hasValue(entry) && entry.response.isSuccessful) {
|
|
||||||
const timeOutdated = entry.response.timeAdded + entry.request.responseMsToLive;
|
|
||||||
const isOutDated = new Date().getTime() > timeOutdated;
|
|
||||||
return !isOutDated;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the request with the specified href is cached
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* The link of the request to check
|
||||||
|
* @return boolean
|
||||||
|
* true if the request with the specified href is cached,
|
||||||
|
* false otherwise
|
||||||
|
*/
|
||||||
|
hasByHref(href: string): boolean {
|
||||||
|
let result = false;
|
||||||
|
this.getByHref(href).pipe(
|
||||||
|
take(1)
|
||||||
|
).subscribe((requestEntry: RequestEntry) => result = this.isValid(requestEntry));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="form-group w-100 pr-2 pl-2">
|
||||||
|
<input type="search"
|
||||||
|
class="form-control"
|
||||||
|
(click)="$event.stopPropagation();"
|
||||||
|
placeholder="{{'dso-selector.placeholder' | translate: { type: type.toString().toLowerCase() } }}"
|
||||||
|
[formControl]="input" dsAutoFocus (keyup.enter)="selectSingleResult()">
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<div class="scrollable-menu list-group">
|
||||||
|
<button class="list-group-item list-group-item-action border-0 disabled"
|
||||||
|
*ngIf="(listEntries$ | async)?.payload.page.length == 0">
|
||||||
|
{{'dso-selector.no-results' | translate: { type: type.toString().toLowerCase() } }}
|
||||||
|
</button>
|
||||||
|
<button *ngFor="let listEntry of (listEntries$ | async)?.payload.page"
|
||||||
|
class="list-group-item list-group-item-action border-0 list-entry"
|
||||||
|
title="{{ listEntry.dspaceObject.name }}"
|
||||||
|
(click)="onSelect.emit(listEntry.dspaceObject)" #listEntryElement>
|
||||||
|
<ds-wrapper-list-element [object]="listEntry"></ds-wrapper-list-element>
|
||||||
|
</button>
|
||||||
|
</div>
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { DSOSelectorComponent } from './dso-selector.component';
|
||||||
|
import { SearchService } from '../../../+search-page/search-service/search.service';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('DSOSelectorComponent', () => {
|
||||||
|
let component: DSOSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<DSOSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const currentDSOId = 'test-uuid-ford-sose';
|
||||||
|
const type = DSpaceObjectType.ITEM;
|
||||||
|
const searchResult = new ItemSearchResult();
|
||||||
|
const item = new Item();
|
||||||
|
item.metadata = {
|
||||||
|
'dc.title': [Object.assign(new MetadataValue(), {
|
||||||
|
value: 'Item title',
|
||||||
|
language: undefined
|
||||||
|
})]
|
||||||
|
};
|
||||||
|
searchResult.dspaceObject = item;
|
||||||
|
searchResult.hitHighlights = {};
|
||||||
|
const searchService = jasmine.createSpyObj('searchService', {
|
||||||
|
search: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(undefined, [searchResult])))
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [DSOSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchService, useValue: searchService },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DSOSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
component.currentDSOId = currentDSOId;
|
||||||
|
component.type = type;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initially call the search method on the SearchService with the given DSO uuid', () => {
|
||||||
|
const searchOptions = new PaginatedSearchOptions({
|
||||||
|
query: currentDSOId,
|
||||||
|
dsoType: type,
|
||||||
|
pagination: (component as any).defaultPagination
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(searchService.search).toHaveBeenCalledWith(searchOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
;
|
@@ -0,0 +1,109 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
QueryList,
|
||||||
|
ViewChildren
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { debounceTime, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
import { SearchService } from '../../../+search-page/search-service/search.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { SearchResult } from '../../../+search-page/search-result.model';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-dso-selector',
|
||||||
|
// styleUrls: ['./dso-selector.component.scss'],
|
||||||
|
templateUrl: './dso-selector.component.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a list of DSO's of which one can be selected
|
||||||
|
* The user can search the list by using the input field
|
||||||
|
*/
|
||||||
|
export class DSOSelectorComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initially selected DSO's uuid
|
||||||
|
*/
|
||||||
|
@Input() currentDSOId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of DSpace objects this components shows a list of
|
||||||
|
*/
|
||||||
|
@Input() type: DSpaceObjectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the selected Object when a user selects it in the list
|
||||||
|
*/
|
||||||
|
@Output() onSelect: EventEmitter<DSpaceObject> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input form control to query the list
|
||||||
|
*/
|
||||||
|
public input: FormControl = new FormControl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default pagination for this feature
|
||||||
|
*/
|
||||||
|
private defaultPagination = { id: 'dso-selector', currentPage: 1, pageSize: 5 } as any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List with search results of DSpace objects for the current query
|
||||||
|
*/
|
||||||
|
listEntries$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of element references to all elements
|
||||||
|
*/
|
||||||
|
@ViewChildren('listEntryElement') listElements: QueryList<ElementRef>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to wait before sending a search request to the server when a user types something
|
||||||
|
*/
|
||||||
|
debounceTime = 500;
|
||||||
|
|
||||||
|
constructor(private searchService: SearchService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the listEntries$ variable with search results based on the input field's current value
|
||||||
|
* The search will always start with the initial currentDSOId value
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.input.setValue(this.currentDSOId);
|
||||||
|
this.listEntries$ = this.input.valueChanges
|
||||||
|
.pipe(
|
||||||
|
debounceTime(this.debounceTime),
|
||||||
|
startWith(this.currentDSOId),
|
||||||
|
switchMap((query) => {
|
||||||
|
return this.searchService.search(
|
||||||
|
new PaginatedSearchOptions({
|
||||||
|
query: query,
|
||||||
|
dsoType: this.type,
|
||||||
|
pagination: this.defaultPagination
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set focus on the first list element when there is only one result
|
||||||
|
*/
|
||||||
|
selectSingleResult(): void {
|
||||||
|
if (this.listElements.length > 0) {
|
||||||
|
this.listElements.first.nativeElement.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import * as collectionRouter from '../../../../+collection-page/collection-page-routing.module';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { CreateCollectionParentSelectorComponent } from './create-collection-parent-selector.component';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('CreateCollectionParentSelectorComponent', () => {
|
||||||
|
let component: CreateCollectionParentSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<CreateCollectionParentSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const community = new Community();
|
||||||
|
community.uuid = '1234-1234-1234-1234';
|
||||||
|
community.metadata = {
|
||||||
|
'dc.title': [
|
||||||
|
Object.assign(new MetadataValue(), {
|
||||||
|
value: 'Community title',
|
||||||
|
language: undefined
|
||||||
|
})]
|
||||||
|
};
|
||||||
|
const router = new RouterStub();
|
||||||
|
const communityRD = new RemoteData(false, false, true, undefined, community);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const createPath = 'testCreatePath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [CreateCollectionParentSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ community: communityRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnProperty(collectionRouter, 'getCollectionCreatePath').and.callFake(() => {
|
||||||
|
return () => createPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateCollectionParentSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(community);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([createPath], { queryParams: { parent: community.uuid } });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {
|
||||||
|
COLLECTION_PARENT_PARAMETER,
|
||||||
|
getCollectionCreatePath
|
||||||
|
} from '../../../../+collection-page/collection-page-routing.module';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a list of existing communities inside a modal
|
||||||
|
* Used to choose a community from to create a new collection in
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-create-collection-parent-selector',
|
||||||
|
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||||
|
})
|
||||||
|
export class CreateCollectionParentSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.COLLECTION;
|
||||||
|
selectorType = DSpaceObjectType.COMMUNITY;
|
||||||
|
action = SelectorActionType.CREATE;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the collection create page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
const navigationExtras: NavigationExtras = {
|
||||||
|
queryParams: {
|
||||||
|
[COLLECTION_PARENT_PARAMETER]: dso.uuid,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.router.navigate([getCollectionCreatePath()], navigationExtras);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
<div>
|
||||||
|
<div class="modal-header">{{'dso-selector.'+ action + '.' + objectType.toString().toLowerCase() + '.head' | translate}}
|
||||||
|
<button type="button" class="close" (click)="close()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<button class="btn btn-outline-primary btn-lg btn-block" (click)="selectObject(undefined)">{{'dso-selector.create.community.top-level' | translate}}</button>
|
||||||
|
<h3 class="position-relative py-1 my-3 font-weight-normal">
|
||||||
|
<hr>
|
||||||
|
<div id="create-community-or-separator" class="text-center position-absolute w-100">
|
||||||
|
<span class="px-4 bg-white">or</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h5 class="px-2">{{'dso-selector.create.community.sub-level' | translate}}</h5>
|
||||||
|
<ds-dso-selector [currentDSOId]="(dsoRD$ | async)?.payload.uuid" [type]="selectorType" (onSelect)="selectObject($event)"></ds-dso-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
#create-community-or-separator {
|
||||||
|
top: 0;
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import * as communityRouter from '../../../../+community-page/community-page-routing.module';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { CreateCommunityParentSelectorComponent } from './create-community-parent-selector.component';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('CreateCommunityParentSelectorComponent', () => {
|
||||||
|
let component: CreateCommunityParentSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<CreateCommunityParentSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const community = new Community();
|
||||||
|
community.uuid = '1234-1234-1234-1234';
|
||||||
|
community.metadata = { 'dc.title': [Object.assign(new MetadataValue(), { value: 'Community title', language: undefined })] };
|
||||||
|
const router = new RouterStub();
|
||||||
|
const communityRD = new RemoteData(false, false, true, undefined, community);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const createPath = 'testCreatePath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [CreateCommunityParentSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ community: communityRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnProperty(communityRouter, 'getCommunityCreatePath').and.callFake(() => {
|
||||||
|
return () => createPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateCommunityParentSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(community);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([createPath], { queryParams: { parent: community.uuid } });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,51 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {
|
||||||
|
COMMUNITY_PARENT_PARAMETER,
|
||||||
|
getCommunityCreatePath
|
||||||
|
} from '../../../../+community-page/community-page-routing.module';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a button - for top communities -
|
||||||
|
* and a list of parent communities - for sub communities
|
||||||
|
* inside a modal
|
||||||
|
* Used to create a new community
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-create-community-parent-selector',
|
||||||
|
styleUrls: ['./create-community-parent-selector.component.scss'],
|
||||||
|
templateUrl: './create-community-parent-selector.component.html',
|
||||||
|
})
|
||||||
|
export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.COMMUNITY;
|
||||||
|
selectorType = DSpaceObjectType.COMMUNITY;
|
||||||
|
action = SelectorActionType.CREATE;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the community create page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
let navigationExtras: NavigationExtras = {};
|
||||||
|
if (hasValue(dso)) {
|
||||||
|
navigationExtras = {
|
||||||
|
queryParams: {
|
||||||
|
[COMMUNITY_PARENT_PARAMETER]: dso.uuid,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.router.navigate([getCommunityCreatePath()], navigationExtras);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { CreateItemParentSelectorComponent } from './create-item-parent-selector.component';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('CreateItemParentSelectorComponent', () => {
|
||||||
|
let component: CreateItemParentSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<CreateItemParentSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const collection = new Collection();
|
||||||
|
collection.uuid = '1234-1234-1234-1234';
|
||||||
|
collection.metadata = { 'dc.title': [Object.assign(new MetadataValue(), { value: 'Collection title', language: undefined })] };
|
||||||
|
const router = new RouterStub();
|
||||||
|
const collectionRD = new RemoteData(false, false, true, undefined, collection);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const createPath = 'testCreatePath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [CreateItemParentSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ collection: collectionRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// spyOnProperty(itemRouter, 'getItemCreatePath').and.callFake(() => {
|
||||||
|
// return () => createPath;
|
||||||
|
// });
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CreateItemParentSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct create path when navigate is called', () => {
|
||||||
|
/* TODO when there is a specific submission path */
|
||||||
|
// component.navigate(item);
|
||||||
|
// expect(router.navigate).toHaveBeenCalledWith([createPath]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a list of existing collections inside a modal
|
||||||
|
* Used to choose a collection from to create a new item in
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-create-item-parent-selector',
|
||||||
|
// styleUrls: ['./create-item-parent-selector.component.scss'],
|
||||||
|
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||||
|
})
|
||||||
|
export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.ITEM;
|
||||||
|
selectorType = DSpaceObjectType.COLLECTION;
|
||||||
|
action = SelectorActionType.CREATE;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the item create page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
// There's no submit path per collection yet...
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<div class="modal-header">{{'dso-selector.'+ action + '.' + objectType.toString().toLowerCase() + '.head' | translate}}
|
||||||
|
<button type="button" class="close" (click)="close()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ds-dso-selector [currentDSOId]="(dsoRD$ | async)?.payload.uuid" [type]="selectorType" (onSelect)="selectObject($event)"></ds-dso-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,135 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { Component, DebugElement, NO_ERRORS_SCHEMA, OnInit } from '@angular/core';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from './dso-selector-modal-wrapper.component';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { DSOSelectorComponent } from '../dso-selector/dso-selector.component';
|
||||||
|
import { MockComponent } from 'ng-mocks';
|
||||||
|
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('DSOSelectorModalWrapperComponent', () => {
|
||||||
|
let component: DSOSelectorModalWrapperComponent;
|
||||||
|
let fixture: ComponentFixture<DSOSelectorModalWrapperComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const item = new Item();
|
||||||
|
item.uuid = '1234-1234-1234-1234';
|
||||||
|
item.metadata = {
|
||||||
|
'dc.title': [Object.assign(new MetadataValue(), {
|
||||||
|
value: 'Item title',
|
||||||
|
language: undefined
|
||||||
|
})]
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemRD = new RemoteData(false, false, true, undefined, item);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [TestComponent, MockComponent(DSOSelectorComponent)],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ item: itemRD }) } } } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TestComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initially set the DSO to the activated route\'s item/collection/community', () => {
|
||||||
|
component.dsoRD$
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((a) => {
|
||||||
|
expect(a).toEqual(itemRD);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('selectObject', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(component, 'navigate');
|
||||||
|
spyOn(component, 'close');
|
||||||
|
component.selectObject(item)
|
||||||
|
});
|
||||||
|
it('should call the close and navigate method on the component with the given DSO', () => {
|
||||||
|
expect(component.close).toHaveBeenCalled();
|
||||||
|
expect(component.navigate).toHaveBeenCalledWith(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('close', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.close();
|
||||||
|
});
|
||||||
|
it('should call the close method on the æctive modal', () => {
|
||||||
|
expect(modalStub.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the onSelect method emits on the child component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(component, 'selectObject');
|
||||||
|
debugElement.query(By.css('ds-dso-selector')).componentInstance.onSelect.emit(item);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('should call the selectObject method on the component with the correct object', () => {
|
||||||
|
expect(component.selectObject).toHaveBeenCalledWith(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the click method emits on close button', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(component, 'close');
|
||||||
|
debugElement.query(By.css('button.close')).triggerEventHandler('click', {});
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('should call the close method on the component', () => {
|
||||||
|
expect(component.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
templateUrl: './dso-selector-modal-wrapper.component.html'
|
||||||
|
})
|
||||||
|
class TestComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.ITEM;
|
||||||
|
selectorType = DSpaceObjectType.ITEM;
|
||||||
|
action = SelectorActionType.EDIT;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
/* comment */
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { Component, Injectable, Input, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
|
||||||
|
export enum SelectorActionType {
|
||||||
|
CREATE = 'create',
|
||||||
|
EDIT = 'edit'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class that represents a wrapper for modal content used to select a DSpace Object
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export abstract class DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The current page's DSO
|
||||||
|
*/
|
||||||
|
@Input() dsoRD$: Observable<RemoteData<DSpaceObject>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the DSO that's being edited or created
|
||||||
|
*/
|
||||||
|
objectType: DSpaceObjectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of DSO that can be selected from this list
|
||||||
|
*/
|
||||||
|
selectorType: DSpaceObjectType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of action to perform
|
||||||
|
*/
|
||||||
|
action: SelectorActionType;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get de current page's DSO based on the selectorType
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
const typeString = this.selectorType.toString().toLowerCase();
|
||||||
|
this.dsoRD$ = this.route.root.firstChild.firstChild.data.pipe(map((data) => data[typeString]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called when an object has been selected
|
||||||
|
* @param dso The selected DSpaceObject
|
||||||
|
*/
|
||||||
|
selectObject(dso: DSpaceObject) {
|
||||||
|
this.close();
|
||||||
|
this.navigate(dso);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a page based on the DSpaceObject provided
|
||||||
|
* @param dso The DSpaceObject which can be used to calculate the page to navigate to
|
||||||
|
*/
|
||||||
|
abstract navigate(dso: DSpaceObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.activeModal.close();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import * as collectionRouter from '../../../../+collection-page/collection-page-routing.module';
|
||||||
|
import { EditCollectionSelectorComponent } from './edit-collection-selector.component';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('EditCollectionSelectorComponent', () => {
|
||||||
|
let component: EditCollectionSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<EditCollectionSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const collection = new Collection();
|
||||||
|
collection.uuid = '1234-1234-1234-1234';
|
||||||
|
collection.metadata = { 'dc.title': [Object.assign(new MetadataValue(), { value: 'Collection title', language: undefined })] };
|
||||||
|
const router = new RouterStub();
|
||||||
|
const collectionRD = new RemoteData(false, false, true, undefined, collection);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const editPath = 'testEditPath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [EditCollectionSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ collection: collectionRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnProperty(collectionRouter, 'getCollectionEditPath').and.callFake(() => {
|
||||||
|
return () => editPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EditCollectionSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(collection);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([editPath]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,36 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { getCollectionEditPath } from '../../../../+collection-page/collection-page-routing.module';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a list of existing collections inside a modal
|
||||||
|
* Used to choose a collection from to edit
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-edit-collection-selector',
|
||||||
|
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||||
|
})
|
||||||
|
export class EditCollectionSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.COLLECTION;
|
||||||
|
selectorType = DSpaceObjectType.COLLECTION;
|
||||||
|
action = SelectorActionType.EDIT;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the collection edit page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
this.router.navigate([getCollectionEditPath(dso.uuid)]);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import * as communityRouter from '../../../../+community-page/community-page-routing.module';
|
||||||
|
import { EditCommunitySelectorComponent } from './edit-community-selector.component';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('EditCommunitySelectorComponent', () => {
|
||||||
|
let component: EditCommunitySelectorComponent;
|
||||||
|
let fixture: ComponentFixture<EditCommunitySelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const community = new Community();
|
||||||
|
community.uuid = '1234-1234-1234-1234';
|
||||||
|
community.metadata = { 'dc.title': [Object.assign(new MetadataValue(), { value: 'Community title', language: undefined })] };
|
||||||
|
const router = new RouterStub();
|
||||||
|
const communityRD = new RemoteData(false, false, true, undefined, community);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const editPath = 'testEditPath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [EditCommunitySelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ community: communityRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnProperty(communityRouter, 'getCommunityEditPath').and.callFake(() => {
|
||||||
|
return () => editPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EditCommunitySelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(community);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([editPath]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,37 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { getCommunityEditPath } from '../../../../+community-page/community-page-routing.module';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a list of existing communities inside a modal
|
||||||
|
* Used to choose a community from to edit
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-edit-community-selector',
|
||||||
|
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
export class EditCommunitySelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.COMMUNITY;
|
||||||
|
selectorType = DSpaceObjectType.COMMUNITY;
|
||||||
|
action = SelectorActionType.EDIT;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the community edit page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
this.router.navigate([getCommunityEditPath(dso.uuid)]);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { EditItemSelectorComponent } from './edit-item-selector.component';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RouterStub } from '../../../testing/router-stub';
|
||||||
|
import * as itemRouter from '../../../../+item-page/item-page-routing.module';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
describe('EditItemSelectorComponent', () => {
|
||||||
|
let component: EditItemSelectorComponent;
|
||||||
|
let fixture: ComponentFixture<EditItemSelectorComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
|
||||||
|
const item = new Item();
|
||||||
|
item.uuid = '1234-1234-1234-1234';
|
||||||
|
item.metadata = { 'dc.title': [Object.assign(new MetadataValue(), { value: 'Item title', language: undefined })] };
|
||||||
|
const router = new RouterStub();
|
||||||
|
const itemRD = new RemoteData(false, false, true, undefined, item);
|
||||||
|
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
|
||||||
|
const editPath = 'testEditPath';
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [EditItemSelectorComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: { root: { firstChild: { firstChild: { data: observableOf({ item: itemRD }) } } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Router, useValue: router
|
||||||
|
}
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnProperty(itemRouter, 'getItemEditPath').and.callFake(() => {
|
||||||
|
return () => editPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(EditItemSelectorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with the correct edit path when navigate is called', () => {
|
||||||
|
component.navigate(item);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([editPath]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { getItemEditPath } from '../../../../+item-page/item-page-routing.module';
|
||||||
|
import {
|
||||||
|
DSOSelectorModalWrapperComponent,
|
||||||
|
SelectorActionType
|
||||||
|
} from '../dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to wrap a list of existing items inside a modal
|
||||||
|
* Used to choose an item from to edit
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-edit-item-selector',
|
||||||
|
templateUrl: '../dso-selector-modal-wrapper.component.html',
|
||||||
|
})
|
||||||
|
export class EditItemSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
|
||||||
|
objectType = DSpaceObjectType.ITEM;
|
||||||
|
selectorType = DSpaceObjectType.ITEM;
|
||||||
|
action = SelectorActionType.EDIT;
|
||||||
|
|
||||||
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
|
super(activeModal, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the item edit page
|
||||||
|
*/
|
||||||
|
navigate(dso: DSpaceObject) {
|
||||||
|
this.router.navigate([getItemEditPath(dso.uuid)]);
|
||||||
|
}
|
||||||
|
}
|
@@ -12,7 +12,7 @@ export enum MenuID {
|
|||||||
* List of possible MenuItemTypes
|
* List of possible MenuItemTypes
|
||||||
*/
|
*/
|
||||||
export enum MenuItemType {
|
export enum MenuItemType {
|
||||||
TEXT, LINK, ALTMETRIC, SEARCH
|
TEXT, LINK, ALTMETRIC, SEARCH, ONCLICK
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
11
src/app/shared/menu/menu-item/models/onclick.model.ts
Normal file
11
src/app/shared/menu/menu-item/models/onclick.model.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { MenuItemModel } from './menu-item.model';
|
||||||
|
import { MenuItemType } from '../../initial-menus-state';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model representing an OnClick Menu Section
|
||||||
|
*/
|
||||||
|
export class OnClickMenuItemModel implements MenuItemModel {
|
||||||
|
type = MenuItemType.ONCLICK;
|
||||||
|
text: string;
|
||||||
|
function: () => {};
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
<a class="nav-item nav-link" role="button" (click)="item.function()">{{item.text | translate}}</a>
|
@@ -0,0 +1,3 @@
|
|||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TextMenuItemComponent } from './text-menu-item.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { OnClickMenuItemComponent } from './onclick-menu-item.component';
|
||||||
|
import { OnClickMenuItemModel } from './models/onclick.model';
|
||||||
|
|
||||||
|
describe('OnClickMenuItemComponent', () => {
|
||||||
|
let component: OnClickMenuItemComponent;
|
||||||
|
let fixture: ComponentFixture<OnClickMenuItemComponent>;
|
||||||
|
let debugElement: DebugElement;
|
||||||
|
const text = 'HELLO';
|
||||||
|
const func = () => {
|
||||||
|
/* comment */
|
||||||
|
};
|
||||||
|
const item = Object.assign(new OnClickMenuItemModel(), { text, function: func });
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
declarations: [OnClickMenuItemComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: 'itemModelProvider', useValue: item },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(item, 'function');
|
||||||
|
fixture = TestBed.createComponent(OnClickMenuItemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the correct text', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the text element', () => {
|
||||||
|
const textContent = debugElement.query(By.css('a')).nativeElement.textContent;
|
||||||
|
expect(textContent).toEqual(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain call the function on the item when clicked', () => {
|
||||||
|
debugElement.query(By.css('a.nav-link')).triggerEventHandler('click', {});
|
||||||
|
expect(item.function).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
20
src/app/shared/menu/menu-item/onclick-menu-item.component.ts
Normal file
20
src/app/shared/menu/menu-item/onclick-menu-item.component.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { MenuItemType } from '../initial-menus-state';
|
||||||
|
import { rendersMenuItemForType } from '../menu-item.decorator';
|
||||||
|
import { OnClickMenuItemModel } from './models/onclick.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a menu section of type ONCLICK
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-onclick-menu-item',
|
||||||
|
styleUrls: ['./onclick-menu-item.component.scss'],
|
||||||
|
templateUrl: './onclick-menu-item.component.html'
|
||||||
|
})
|
||||||
|
@rendersMenuItemForType(MenuItemType.ONCLICK)
|
||||||
|
export class OnClickMenuItemComponent {
|
||||||
|
item: OnClickMenuItemModel;
|
||||||
|
constructor(@Inject('itemModelProvider') item: OnClickMenuItemModel) {
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
}
|
@@ -5,17 +5,20 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { LinkMenuItemComponent } from './menu-item/link-menu-item.component';
|
import { LinkMenuItemComponent } from './menu-item/link-menu-item.component';
|
||||||
import { TextMenuItemComponent } from './menu-item/text-menu-item.component';
|
import { TextMenuItemComponent } from './menu-item/text-menu-item.component';
|
||||||
|
import { OnClickMenuItemComponent } from './menu-item/onclick-menu-item.component';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
MenuSectionComponent,
|
MenuSectionComponent,
|
||||||
MenuComponent,
|
MenuComponent,
|
||||||
LinkMenuItemComponent,
|
LinkMenuItemComponent,
|
||||||
TextMenuItemComponent
|
TextMenuItemComponent,
|
||||||
|
OnClickMenuItemComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
LinkMenuItemComponent,
|
LinkMenuItemComponent,
|
||||||
TextMenuItemComponent
|
TextMenuItemComponent,
|
||||||
|
OnClickMenuItemComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
|
@@ -97,6 +97,17 @@ import { AutoFocusDirective } from './utils/auto-focus.directive';
|
|||||||
import { ComcolPageBrowseByComponent } from './comcol-page-browse-by/comcol-page-browse-by.component';
|
import { ComcolPageBrowseByComponent } from './comcol-page-browse-by/comcol-page-browse-by.component';
|
||||||
import { StartsWithDateComponent } from './starts-with/date/starts-with-date.component';
|
import { StartsWithDateComponent } from './starts-with/date/starts-with-date.component';
|
||||||
import { StartsWithTextComponent } from './starts-with/text/starts-with-text.component';
|
import { StartsWithTextComponent } from './starts-with/text/starts-with-text.component';
|
||||||
|
import { DSOSelectorComponent } from './dso-selector/dso-selector/dso-selector.component';
|
||||||
|
import { CreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
||||||
|
import { CreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||||
|
import { CreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
||||||
|
import { CommunitySearchResultListElementComponent } from './object-list/search-result-list-element/community-search-result/community-search-result-list-element.component';
|
||||||
|
import { CollectionSearchResultListElementComponent } from './object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component';
|
||||||
|
import { ItemSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-search-result-list-element.component';
|
||||||
|
import { EditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
||||||
|
import { EditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
||||||
|
import { EditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
||||||
|
import { DSOSelectorModalWrapperComponent } from './dso-selector/modal-wrappers/dso-selector-modal-wrapper.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -179,6 +190,16 @@ const COMPONENTS = [
|
|||||||
TruncatablePartComponent,
|
TruncatablePartComponent,
|
||||||
BrowseByComponent,
|
BrowseByComponent,
|
||||||
InputSuggestionsComponent,
|
InputSuggestionsComponent,
|
||||||
|
DSOSelectorComponent,
|
||||||
|
CreateCommunityParentSelectorComponent,
|
||||||
|
CreateCollectionParentSelectorComponent,
|
||||||
|
CreateItemParentSelectorComponent,
|
||||||
|
EditCommunitySelectorComponent,
|
||||||
|
EditCollectionSelectorComponent,
|
||||||
|
EditItemSelectorComponent,
|
||||||
|
CommunitySearchResultListElementComponent,
|
||||||
|
CollectionSearchResultListElementComponent,
|
||||||
|
ItemSearchResultListElementComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -187,13 +208,23 @@ const ENTRY_COMPONENTS = [
|
|||||||
CollectionListElementComponent,
|
CollectionListElementComponent,
|
||||||
CommunityListElementComponent,
|
CommunityListElementComponent,
|
||||||
SearchResultListElementComponent,
|
SearchResultListElementComponent,
|
||||||
|
CommunitySearchResultListElementComponent,
|
||||||
|
CollectionSearchResultListElementComponent,
|
||||||
|
ItemSearchResultListElementComponent,
|
||||||
ItemGridElementComponent,
|
ItemGridElementComponent,
|
||||||
CollectionGridElementComponent,
|
CollectionGridElementComponent,
|
||||||
CommunityGridElementComponent,
|
CommunityGridElementComponent,
|
||||||
SearchResultGridElementComponent,
|
SearchResultGridElementComponent,
|
||||||
BrowseEntryListElementComponent,
|
BrowseEntryListElementComponent,
|
||||||
StartsWithDateComponent,
|
StartsWithDateComponent,
|
||||||
StartsWithTextComponent
|
StartsWithTextComponent,
|
||||||
|
DSOSelectorComponent,
|
||||||
|
CreateCommunityParentSelectorComponent,
|
||||||
|
CreateCollectionParentSelectorComponent,
|
||||||
|
CreateItemParentSelectorComponent,
|
||||||
|
EditCommunitySelectorComponent,
|
||||||
|
EditCollectionSelectorComponent,
|
||||||
|
EditItemSelectorComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
|
@@ -40,4 +40,8 @@ export class SearchServiceStub {
|
|||||||
getFilterLabels() {
|
getFilterLabels() {
|
||||||
return observableOf([]);
|
return observableOf([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
search() {
|
||||||
|
return observableOf({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user