mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Finalise menu refactor, add typedocs and tests
This commit is contained in:
@@ -12,12 +12,16 @@ import { By } from '@angular/platform-browser';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||
import { MenuItemModels } from '../../../shared/menu/menu-section.model';
|
||||
|
||||
describe('ExpandableAdminSidebarSectionComponent', () => {
|
||||
let component: ExpandableAdminSidebarSectionComponent;
|
||||
let fixture: ComponentFixture<ExpandableAdminSidebarSectionComponent>;
|
||||
const menuService = new MenuServiceStub();
|
||||
const iconString = 'test';
|
||||
|
||||
|
||||
describe('when there are subsections', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule, TranslateModule.forRoot()],
|
||||
@@ -37,7 +41,11 @@ describe('ExpandableAdminSidebarSectionComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([{
|
||||
id: 'test',
|
||||
visible: true,
|
||||
model: {} as MenuItemModels
|
||||
}]));
|
||||
fixture = TestBed.createComponent(ExpandableAdminSidebarSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
|
||||
@@ -69,6 +77,47 @@ describe('ExpandableAdminSidebarSectionComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('when there are no subsections', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule, TranslateModule.forRoot()],
|
||||
declarations: [ExpandableAdminSidebarSectionComponent, TestComponent],
|
||||
providers: [
|
||||
{provide: 'sectionDataProvider', useValue: {icon: iconString, model: {}}},
|
||||
{provide: MenuService, useValue: menuService},
|
||||
{provide: CSSVariableService, useClass: CSSVariableServiceStub},
|
||||
{provide: Router, useValue: new RouterStub()},
|
||||
]
|
||||
}).overrideComponent(ExpandableAdminSidebarSectionComponent, {
|
||||
set: {
|
||||
entryComponents: [TestComponent]
|
||||
}
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
|
||||
fixture = TestBed.createComponent(ExpandableAdminSidebarSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not contain a section', () => {
|
||||
const icon = fixture.debugElement.query(By.css('.shortcut-icon'));
|
||||
expect(icon).toBeNull();
|
||||
const sidebarToggler = fixture.debugElement.query(By.css('.sidebar-section'));
|
||||
expect(sidebarToggler).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
|
@@ -36,7 +36,6 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
|
||||
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||
import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
||||
// import { resolveStaticMenus } from './shared/menu/menu.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -48,8 +47,6 @@ import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
||||
canActivate: [AuthBlockingGuard],
|
||||
canActivateChild: [ServerCheckGuard],
|
||||
resolve: [
|
||||
// resolveStaticMenus(),
|
||||
// MenuResolver,
|
||||
],
|
||||
children: [
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
|
@@ -4,7 +4,6 @@ import { BrowseByGuard } from './browse-by-guard';
|
||||
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
||||
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -13,7 +12,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: BrowseByDSOBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
@@ -1,423 +0,0 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { MenuResolver } from './menu.resolver';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AdminSidebarComponent } from './admin/admin-sidebar/admin-sidebar.component';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||
import { ScriptDataService } from './core/data/processes/script-data.service';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { MenuServiceStub } from './shared/testing/menu-service.stub';
|
||||
import { MenuID } from './shared/menu/menu-id.model';
|
||||
import { BrowseService } from './core/browse/browse.service';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import createSpy = jasmine.createSpy;
|
||||
import { createSuccessfulRemoteDataObject$ } from './shared/remote-data.utils';
|
||||
import { createPaginatedList } from './shared/testing/utils.test';
|
||||
|
||||
const BOOLEAN = { t: true, f: false };
|
||||
const MENU_STATE = {
|
||||
id: 'some menu'
|
||||
};
|
||||
const BROWSE_DEFINITIONS = [
|
||||
{ id: 'definition1' },
|
||||
{ id: 'definition2' },
|
||||
{ id: 'definition3' },
|
||||
];
|
||||
|
||||
describe('MenuResolver', () => {
|
||||
let resolver: MenuResolver;
|
||||
|
||||
let menuService;
|
||||
let browseService;
|
||||
let authorizationService;
|
||||
let scriptService;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
menuService = new MenuServiceStub();
|
||||
spyOn(menuService, 'getMenu').and.returnValue(observableOf(MENU_STATE));
|
||||
spyOn(menuService, 'addSection');
|
||||
|
||||
browseService = jasmine.createSpyObj('browseService', {
|
||||
getBrowseDefinitions: createSuccessfulRemoteDataObject$(createPaginatedList(BROWSE_DEFINITIONS))
|
||||
});
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true)
|
||||
});
|
||||
scriptService = jasmine.createSpyObj('scriptService', {
|
||||
scriptWithNameExistsAndCanExecute: observableOf(true)
|
||||
});
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
||||
declarations: [AdminSidebarComponent],
|
||||
providers: [
|
||||
{ provide: MenuService, useValue: menuService },
|
||||
{ provide: BrowseService, useValue: browseService },
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{
|
||||
provide: NgbModal, useValue: {
|
||||
open: () => {/*comment*/
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
resolver = TestBed.inject(MenuResolver);
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
expect(resolver).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
it('should create all menus', (done) => {
|
||||
spyOn(resolver, 'createPublicMenu$').and.returnValue(observableOf(true));
|
||||
spyOn(resolver, 'createAdminMenu$').and.returnValue(observableOf(true));
|
||||
|
||||
resolver.resolve(null, null).subscribe(resolved => {
|
||||
expect(resolved).toBeTrue();
|
||||
expect(resolver.createPublicMenu$).toHaveBeenCalled();
|
||||
expect(resolver.createAdminMenu$).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an Observable that emits true as soon as all menus are created', () => {
|
||||
spyOn(resolver, 'createPublicMenu$').and.returnValue(cold('--(t|)', BOOLEAN));
|
||||
spyOn(resolver, 'createAdminMenu$').and.returnValue(cold('----(t|)', BOOLEAN));
|
||||
|
||||
expect(resolver.resolve(null, null)).toBeObservable(cold('----(t|)', BOOLEAN));
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPublicMenu$', () => {
|
||||
it('should retrieve the menu by ID return an Observable that emits true as soon as it is created', () => {
|
||||
(menuService as any).getMenu.and.returnValue(cold('--u--m--', {
|
||||
u: undefined,
|
||||
m: MENU_STATE,
|
||||
}));
|
||||
|
||||
expect(resolver.createPublicMenu$()).toBeObservable(cold('-----(t|)', BOOLEAN));
|
||||
expect(menuService.getMenu).toHaveBeenCalledOnceWith(MenuID.PUBLIC);
|
||||
});
|
||||
|
||||
describe('contents', () => {
|
||||
beforeEach((done) => {
|
||||
resolver.createPublicMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should include community list link', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, jasmine.objectContaining({
|
||||
id: 'browse_global_communities_and_collections', visible: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should include browse dropdown', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, jasmine.objectContaining({
|
||||
id: 'browse_global_by_definition1', parentID: 'browse_global', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, jasmine.objectContaining({
|
||||
id: 'browse_global_by_definition2', parentID: 'browse_global', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, jasmine.objectContaining({
|
||||
id: 'browse_global_by_definition3', parentID: 'browse_global', visible: true,
|
||||
}));
|
||||
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, jasmine.objectContaining({
|
||||
id: 'browse_global', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAdminMenu$', () => {
|
||||
const dontShowAdminSections = () => {
|
||||
it('should not show site admin section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'admin_search', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'registries', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
parentID: 'registries', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'curation_tasks', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'workflow', visible: false,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should not show access control section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'access_control', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
parentID: 'access_control', visible: false,
|
||||
}));
|
||||
});
|
||||
|
||||
// We check that the menu section has not been called with visible set to true
|
||||
// The reason why we don't check if it has been called with visible set to false
|
||||
// Is because the function does not get called unless a user is authorised
|
||||
it('should not show the import section', () => {
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'import', visible: true,
|
||||
}));
|
||||
});
|
||||
|
||||
// We check that the menu section has not been called with visible set to true
|
||||
// The reason why we don't check if it has been called with visible set to false
|
||||
// Is because the function does not get called unless a user is authorised
|
||||
it('should not show the export section', () => {
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'export', visible: true,
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
const dontShowNewSection = () => {
|
||||
it('should not show the "New" section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new_community', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new_collection', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new_item', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new', visible: false,
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
const dontShowEditSection = () => {
|
||||
it('should not show the "Edit" section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_community', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_collection', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_item', visible: false,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit', visible: false,
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
it('should retrieve the menu by ID return an Observable that emits true as soon as it is created', () => {
|
||||
(menuService as any).getMenu.and.returnValue(cold('--u--m', {
|
||||
u: undefined,
|
||||
m: MENU_STATE,
|
||||
}));
|
||||
|
||||
expect(resolver.createAdminMenu$()).toBeObservable(cold('-----(t|)', BOOLEAN));
|
||||
expect(menuService.getMenu).toHaveBeenCalledOnceWith(MenuID.ADMIN);
|
||||
});
|
||||
|
||||
describe('for regular user', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID) => {
|
||||
return observableOf(false);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
dontShowAdminSections();
|
||||
dontShowNewSection();
|
||||
dontShowEditSection();
|
||||
});
|
||||
|
||||
describe('regular user who can submit', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized')
|
||||
.and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.CanSubmit);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show "New Item" section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new_item', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new', visible: true,
|
||||
}));
|
||||
});
|
||||
|
||||
dontShowAdminSections();
|
||||
dontShowEditSection();
|
||||
});
|
||||
|
||||
describe('regular user who can edit items', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized')
|
||||
.and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.CanEditItem);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show "Edit Item" section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_item', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit', visible: true,
|
||||
}));
|
||||
});
|
||||
|
||||
dontShowAdminSections();
|
||||
dontShowNewSection();
|
||||
});
|
||||
|
||||
describe('for site admin', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.AdministratorOf);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show new_process', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'new_process', visible: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should contain site admin section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'admin_search', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'registries', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
parentID: 'registries', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'curation_tasks', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'workflow', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'workflow', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'import', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'import_batch', parentID: 'import', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'export', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'export_batch', parentID: 'export', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('for community admin', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.IsCommunityAdmin);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show edit_community', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_community', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('for collection admin', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.IsCollectionAdmin);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show edit_collection', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'edit_collection', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('for group admin', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
|
||||
return observableOf(featureID === FeatureID.CanManageGroups);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach((done) => {
|
||||
resolver.createAdminMenu$().subscribe((_) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show access control section', () => {
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
id: 'access_control', visible: true,
|
||||
}));
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({
|
||||
parentID: 'access_control', visible: true,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,703 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { combineLatest as observableCombineLatest, combineLatest, Observable } from 'rxjs';
|
||||
import { MenuID } from './shared/menu/menu-id.model';
|
||||
import { MenuState } from './shared/menu/menu-state.model';
|
||||
import { MenuItemType } from './shared/menu/menu-item-type.model';
|
||||
import { LinkMenuItemModel } from './shared/menu/menu-item/models/link.model';
|
||||
import { getFirstCompletedRemoteData } from './core/shared/operators';
|
||||
import { PaginatedList } from './core/data/paginated-list.model';
|
||||
import { BrowseDefinition } from './core/shared/browse-definition.model';
|
||||
import { RemoteData } from './core/data/remote-data';
|
||||
import { TextMenuItemModel } from './shared/menu/menu-item/models/text.model';
|
||||
import { BrowseService } from './core/browse/browse.service';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { filter, find, map, take } from 'rxjs/operators';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||
import {
|
||||
ThemedCreateCommunityParentSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component';
|
||||
import { OnClickMenuItemModel } from './shared/menu/menu-item/models/onclick.model';
|
||||
import {
|
||||
ThemedCreateCollectionParentSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component';
|
||||
import {
|
||||
ThemedCreateItemParentSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component';
|
||||
import {
|
||||
ThemedEditCommunitySelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component';
|
||||
import {
|
||||
ThemedEditCollectionSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component';
|
||||
import {
|
||||
ThemedEditItemSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component';
|
||||
import {
|
||||
ExportMetadataSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
METADATA_EXPORT_SCRIPT_NAME,
|
||||
METADATA_IMPORT_SCRIPT_NAME,
|
||||
ScriptDataService
|
||||
} from './core/data/processes/script-data.service';
|
||||
import {
|
||||
ExportBatchSelectorComponent
|
||||
} from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component';
|
||||
|
||||
/**
|
||||
* Creates all of the app's menus
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MenuResolver implements Resolve<boolean> {
|
||||
constructor(
|
||||
protected menuService: MenuService,
|
||||
protected browseService: BrowseService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
return combineLatest([
|
||||
this.createPublicMenu$(),
|
||||
this.createAdminMenu$(),
|
||||
]).pipe(
|
||||
map((menusDone: boolean[]) => menusDone.every(Boolean)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a specific menu to appear
|
||||
* @param id the ID of the menu to wait for
|
||||
* @return an Observable that emits true as soon as the menu is created
|
||||
*/
|
||||
protected waitForMenu$(id: MenuID): Observable<boolean> {
|
||||
return this.menuService.getMenu(id).pipe(
|
||||
find((menu: MenuState) => hasValue(menu)),
|
||||
map(() => true),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.PUBLIC}
|
||||
*/
|
||||
createPublicMenu$(): Observable<boolean> {
|
||||
const menuList: any[] = [
|
||||
/* Communities & Collections tree */
|
||||
{
|
||||
id: `browse_global_communities_and_collections`,
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 0,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_communities_and_collections`,
|
||||
link: `/community-list`
|
||||
} as LinkMenuItemModel
|
||||
}
|
||||
];
|
||||
// Read the different Browse-By types from config and add them to the browse menu
|
||||
this.browseService.getBrowseDefinitions()
|
||||
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
|
||||
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||
if (browseDefListRD.hasSucceeded) {
|
||||
browseDefListRD.payload.page.forEach((browseDef: BrowseDefinition) => {
|
||||
menuList.push({
|
||||
id: `browse_global_by_${browseDef.id}`,
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_by_${browseDef.id}`,
|
||||
link: `/browse/${browseDef.id}`
|
||||
} as LinkMenuItemModel
|
||||
});
|
||||
});
|
||||
menuList.push(
|
||||
/* Browse */
|
||||
{
|
||||
id: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 1,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.browse_global'
|
||||
} as TextMenuItemModel,
|
||||
}
|
||||
);
|
||||
}
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true
|
||||
})));
|
||||
});
|
||||
|
||||
return this.waitForMenu$(MenuID.PUBLIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.ADMIN}
|
||||
*/
|
||||
createAdminMenu$() {
|
||||
this.createMainMenuSections();
|
||||
this.createSiteAdministratorMenuSections();
|
||||
this.createExportMenuSections();
|
||||
this.createImportMenuSections();
|
||||
this.createAccessControlMenuSections();
|
||||
|
||||
return this.waitForMenu$(MenuID.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the main menu sections.
|
||||
* edit_community / edit_collection is only included if the current user is a Community or Collection admin
|
||||
*/
|
||||
createMainMenuSections() {
|
||||
combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
||||
]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem]) => {
|
||||
const newSubMenuList = [
|
||||
{
|
||||
id: 'new_community',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCommunityParentSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_collection',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCollectionParentSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_item',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: canSubmit,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateItemParentSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_process',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.new_process',
|
||||
link: '/processes/new'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
];
|
||||
const editSubMenuList = [
|
||||
/* Edit */
|
||||
{
|
||||
id: 'edit_community',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCommunitySelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_collection',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCollectionAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCollectionSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_item',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditItemSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
];
|
||||
const newSubMenu = {
|
||||
id: 'new',
|
||||
active: false,
|
||||
visible: newSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.new'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'plus',
|
||||
index: 0
|
||||
};
|
||||
const editSubMenu = {
|
||||
id: 'edit',
|
||||
active: false,
|
||||
visible: editSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.edit'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 1
|
||||
};
|
||||
|
||||
const menuList = [
|
||||
...newSubMenuList,
|
||||
newSubMenu,
|
||||
...editSubMenuList,
|
||||
editSubMenu,
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'new_item_version',
|
||||
// parentID: 'new',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.new_item_version',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
|
||||
/* Statistics */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'statistics_task',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.statistics_task',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'chart-bar',
|
||||
// index: 8
|
||||
// },
|
||||
|
||||
/* Control Panel */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'control_panel',
|
||||
// active: false,
|
||||
// visible: isSiteAdmin,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.control_panel',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'cogs',
|
||||
// index: 9
|
||||
// },
|
||||
|
||||
/* Processes */
|
||||
{
|
||||
id: 'processes',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.processes',
|
||||
link: '/processes'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'terminal',
|
||||
index: 10
|
||||
},
|
||||
{
|
||||
id: 'health',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.health',
|
||||
link: '/health'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'heartbeat',
|
||||
index: 11
|
||||
},
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the export scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createExportMenuSections() {
|
||||
const menuList = [
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_community',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_community',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_collection',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_collection',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_item',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_item',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME)
|
||||
]).pipe(
|
||||
filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists),
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
// Hides the export menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-export',
|
||||
index: 3,
|
||||
shouldPersistOnRouteChange: true
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_metadata',
|
||||
parentID: 'export',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_metadata',
|
||||
function: () => {
|
||||
this.modalService.open(ExportMetadataSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_batch',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_batch',
|
||||
function: () => {
|
||||
this.modalService.open(ExportBatchSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the import scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createImportMenuSections() {
|
||||
const menuList = [];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME)
|
||||
]).pipe(
|
||||
filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists),
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
// Hides the import menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.import'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-import',
|
||||
index: 2,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_metadata',
|
||||
parentID: 'import',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_metadata',
|
||||
link: '/admin/metadata-import'
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_batch',
|
||||
parentID: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_batch',
|
||||
link: '/admin/batch-import'
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
createSiteAdministratorMenuSections() {
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf).subscribe((authorized) => {
|
||||
const menuList = [
|
||||
/* Admin Search */
|
||||
{
|
||||
id: 'admin_search',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.admin_search',
|
||||
link: '/admin/search'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'search',
|
||||
index: 5
|
||||
},
|
||||
/* Registries */
|
||||
{
|
||||
id: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.registries'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'list',
|
||||
index: 6
|
||||
},
|
||||
{
|
||||
id: 'registries_metadata',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_metadata',
|
||||
link: 'admin/registries/metadata'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'registries_format',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_format',
|
||||
link: 'admin/registries/bitstream-formats'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
|
||||
/* Curation tasks */
|
||||
{
|
||||
id: 'curation_tasks',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.curation_task',
|
||||
link: 'admin/curation-tasks'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'filter',
|
||||
index: 7
|
||||
},
|
||||
|
||||
/* Workflow */
|
||||
{
|
||||
id: 'workflow',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.workflow',
|
||||
link: '/admin/workflow'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
index: 11
|
||||
},
|
||||
{
|
||||
id: 'system_wide_alert',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.system-wide-alert',
|
||||
link: '/admin/system-wide-alert'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'exclamation-circle',
|
||||
index: 12
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user can manage access control groups
|
||||
*/
|
||||
createAccessControlMenuSections() {
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanManageGroups)
|
||||
]).subscribe(([isSiteAdmin, canManageGroups]) => {
|
||||
const menuList = [
|
||||
/* Access Control */
|
||||
{
|
||||
id: 'access_control_people',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_people',
|
||||
link: '/access-control/epeople'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_groups',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_groups',
|
||||
link: '/access-control/groups'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_bulk',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_bulk',
|
||||
link: '/access-control/bulk-access'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'access_control_authorizations',
|
||||
// parentID: 'access_control',
|
||||
// active: false,
|
||||
// visible: authorized,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.access_control_authorizations',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
{
|
||||
id: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups || isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.access_control'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'key',
|
||||
index: 4
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ import { MenuService } from '../../shared/menu/menu.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { MenuItemModels } from '../../shared/menu/menu-section.model';
|
||||
|
||||
describe('ExpandableNavbarSectionComponent', () => {
|
||||
let component: ExpandableNavbarSectionComponent;
|
||||
@@ -35,7 +36,7 @@ describe('ExpandableNavbarSectionComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([{id: 'test', visible: true, model: {} as MenuItemModels}]));
|
||||
|
||||
fixture = TestBed.createComponent(ExpandableNavbarSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -184,7 +185,7 @@ describe('ExpandableNavbarSectionComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([{id: 'test', visible: true, model: {} as MenuItemModels}]));
|
||||
|
||||
fixture = TestBed.createComponent(ExpandableNavbarSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -195,6 +196,7 @@ describe('ExpandableNavbarSectionComponent', () => {
|
||||
describe('when the mouse enters the section header', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'activateSection');
|
||||
console.log(fixture.nativeElement.innerHTML);
|
||||
const sidebarToggler = fixture.debugElement.query(By.css('div.nav-item.dropdown > a'));
|
||||
sidebarToggler.triggerEventHandler('mouseenter', {
|
||||
preventDefault: () => {/**/
|
||||
|
@@ -1,259 +0,0 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { MenuServiceStub } from '../testing/menu-service.stub';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AdminSidebarComponent } from '../../admin/admin-sidebar/admin-sidebar.component';
|
||||
import { MenuService } from '../menu/menu.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { DSOEditMenuResolver } from './dso-edit-menu.resolver';
|
||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||
import { MenuID } from '../menu/menu-id.model';
|
||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||
import { TextMenuItemModel } from '../menu/menu-item/models/text.model';
|
||||
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
|
||||
describe('DSOEditMenuResolver', () => {
|
||||
|
||||
const MENU_STATE = {
|
||||
id: 'some menu'
|
||||
};
|
||||
|
||||
let resolver: DSOEditMenuResolver;
|
||||
|
||||
let dSpaceObjectDataService;
|
||||
let menuService;
|
||||
let authorizationService;
|
||||
let dsoVersioningModalService;
|
||||
let researcherProfileService;
|
||||
let notificationsService;
|
||||
let translate;
|
||||
|
||||
const route = {
|
||||
data: {
|
||||
menu: {
|
||||
'statistics': [{
|
||||
id: 'statistics-dummy-1',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
}]
|
||||
}
|
||||
},
|
||||
params: {id: 'test-uuid'},
|
||||
};
|
||||
|
||||
const state = {
|
||||
url: 'test-url'
|
||||
};
|
||||
|
||||
const testObject = Object.assign(new Item(), {uuid: 'test-uuid', type: 'item', _links: {self: {href: 'self-link'}}});
|
||||
|
||||
const dummySections1 = [{
|
||||
id: 'dummy-1',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
},
|
||||
{
|
||||
id: 'dummy-2',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
}];
|
||||
|
||||
const dummySections2 = [{
|
||||
id: 'dummy-3',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
},
|
||||
{
|
||||
id: 'dummy-4',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
},
|
||||
{
|
||||
id: 'dummy-5',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
}];
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
menuService = new MenuServiceStub();
|
||||
spyOn(menuService, 'getMenu').and.returnValue(observableOf(MENU_STATE));
|
||||
|
||||
dSpaceObjectDataService = jasmine.createSpyObj('dSpaceObjectDataService', {
|
||||
findById: createSuccessfulRemoteDataObject$(testObject)
|
||||
});
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true)
|
||||
});
|
||||
dsoVersioningModalService = jasmine.createSpyObj('dsoVersioningModalService', {
|
||||
isNewVersionButtonDisabled: observableOf(false),
|
||||
getVersioningTooltipMessage: observableOf('message'),
|
||||
openCreateVersionModal: {}
|
||||
});
|
||||
researcherProfileService = jasmine.createSpyObj('researcherProfileService', {
|
||||
createFromExternalSourceAndReturnRelatedItemId: observableOf('mock-id'),
|
||||
});
|
||||
translate = jasmine.createSpyObj('translate', {
|
||||
get: observableOf('translated-message'),
|
||||
});
|
||||
notificationsService = jasmine.createSpyObj('notificationsService', {
|
||||
success: {},
|
||||
error: {},
|
||||
});
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
||||
declarations: [AdminSidebarComponent],
|
||||
providers: [
|
||||
{provide: DSpaceObjectDataService, useValue: dSpaceObjectDataService},
|
||||
{provide: MenuService, useValue: menuService},
|
||||
{provide: AuthorizationDataService, useValue: authorizationService},
|
||||
{provide: DsoVersioningModalService, useValue: dsoVersioningModalService},
|
||||
{provide: ResearcherProfileDataService, useValue: researcherProfileService},
|
||||
{provide: TranslateService, useValue: translate},
|
||||
{provide: NotificationsService, useValue: notificationsService},
|
||||
{
|
||||
provide: NgbModal, useValue: {
|
||||
open: () => {/*comment*/
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
resolver = TestBed.inject(DSOEditMenuResolver);
|
||||
|
||||
spyOn(menuService, 'addSection');
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
expect(resolver).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
it('should create all menus when a dso is found based on the route id param', (done) => {
|
||||
spyOn(resolver, 'getDsoMenus').and.returnValue(
|
||||
[observableOf(dummySections1), observableOf(dummySections2)]
|
||||
);
|
||||
resolver.resolve(route as any, null).subscribe(resolved => {
|
||||
expect(resolved).toEqual(
|
||||
{
|
||||
...route.data.menu,
|
||||
[MenuID.DSO_EDIT]: [
|
||||
...dummySections1.map((menu) => Object.assign(menu, {id: menu.id + '-test-uuid'})),
|
||||
...dummySections2.map((menu) => Object.assign(menu, {id: menu.id + '-test-uuid'}))
|
||||
]
|
||||
}
|
||||
);
|
||||
expect(dSpaceObjectDataService.findById).toHaveBeenCalledWith('test-uuid', true, false);
|
||||
expect(resolver.getDsoMenus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should create all menus when a dso is found based on the route scope query param when no id param is present', (done) => {
|
||||
spyOn(resolver, 'getDsoMenus').and.returnValue(
|
||||
[observableOf(dummySections1), observableOf(dummySections2)]
|
||||
);
|
||||
const routeWithScope = {
|
||||
data: {
|
||||
menu: {
|
||||
'statistics': [{
|
||||
id: 'statistics-dummy-1',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: null
|
||||
}]
|
||||
}
|
||||
},
|
||||
params: {},
|
||||
queryParams: {scope: 'test-scope-uuid'},
|
||||
};
|
||||
|
||||
resolver.resolve(routeWithScope as any, null).subscribe(resolved => {
|
||||
expect(resolved).toEqual(
|
||||
{
|
||||
...route.data.menu,
|
||||
[MenuID.DSO_EDIT]: [
|
||||
...dummySections1.map((menu) => Object.assign(menu, {id: menu.id + '-test-scope-uuid'})),
|
||||
...dummySections2.map((menu) => Object.assign(menu, {id: menu.id + '-test-scope-uuid'}))
|
||||
]
|
||||
}
|
||||
);
|
||||
expect(dSpaceObjectDataService.findById).toHaveBeenCalledWith('test-scope-uuid', true, false);
|
||||
expect(resolver.getDsoMenus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should return the statistics menu when no dso is found', (done) => {
|
||||
(dSpaceObjectDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
|
||||
|
||||
resolver.resolve(route as any, null).subscribe(resolved => {
|
||||
expect(resolved).toEqual(
|
||||
{
|
||||
...route.data.menu
|
||||
}
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('getDsoMenus', () => {
|
||||
it('should return as first part the item version, orcid and claim list ', (done) => {
|
||||
const result = resolver.getDsoMenus(testObject, route, state);
|
||||
result[0].subscribe((menuList) => {
|
||||
expect(menuList.length).toEqual(3);
|
||||
expect(menuList[0].id).toEqual('orcid-dso');
|
||||
expect(menuList[0].active).toEqual(false);
|
||||
// Visible should be false due to the item not being of type person
|
||||
expect(menuList[0].visible).toEqual(false);
|
||||
expect(menuList[0].model.type).toEqual(MenuItemType.LINK);
|
||||
|
||||
expect(menuList[1].id).toEqual('version-dso');
|
||||
expect(menuList[1].active).toEqual(false);
|
||||
expect(menuList[1].visible).toEqual(true);
|
||||
expect(menuList[1].model.type).toEqual(MenuItemType.ONCLICK);
|
||||
expect((menuList[1].model as TextMenuItemModel).text).toEqual('message');
|
||||
expect(menuList[1].model.disabled).toEqual(false);
|
||||
expect(menuList[1].icon).toEqual('code-branch');
|
||||
|
||||
expect(menuList[2].id).toEqual('claim-dso');
|
||||
expect(menuList[2].active).toEqual(false);
|
||||
// Visible should be false due to the item not being of type person
|
||||
expect(menuList[2].visible).toEqual(false);
|
||||
expect(menuList[2].model.type).toEqual(MenuItemType.ONCLICK);
|
||||
expect((menuList[2].model as TextMenuItemModel).text).toEqual('item.page.claim.button');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
it('should return as second part the common list ', (done) => {
|
||||
const result = resolver.getDsoMenus(testObject, route, state);
|
||||
result[1].subscribe((menuList) => {
|
||||
expect(menuList.length).toEqual(1);
|
||||
expect(menuList[0].id).toEqual('edit-dso');
|
||||
expect(menuList[0].active).toEqual(false);
|
||||
expect(menuList[0].visible).toEqual(true);
|
||||
expect(menuList[0].model.type).toEqual(MenuItemType.LINK);
|
||||
expect((menuList[0].model as LinkMenuItemModel).text).toEqual('item.page.edit');
|
||||
expect((menuList[0].model as LinkMenuItemModel).link).toEqual('/items/test-uuid/edit/metadata');
|
||||
expect(menuList[0].icon).toEqual('pencil-alt');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,229 +0,0 @@
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { MenuService } from '../menu/menu.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from '../empty.util';
|
||||
import { MenuID } from '../menu/menu-id.model';
|
||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||
import { MenuSection } from '../menu/menu-section.model';
|
||||
import { getDSORoute } from '../../app-routing-paths';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Creates the menus for the dspace object pages
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection[] }> {
|
||||
|
||||
constructor(
|
||||
protected dSpaceObjectDataService: DSpaceObjectDataService,
|
||||
protected menuService: MenuService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected dsoVersioningModalService: DsoVersioningModalService,
|
||||
protected researcherProfileService: ResearcherProfileDataService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise all dspace object related menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{ [key: string]: MenuSection[] }> {
|
||||
let id = route.params.id;
|
||||
if (hasNoValue(id) && hasValue(route.queryParams.scope)) {
|
||||
id = route.queryParams.scope;
|
||||
}
|
||||
if (hasNoValue(id)) {
|
||||
// If there's no ID, we're not on a DSO homepage, so pass on any pre-existing menu route data
|
||||
return observableOf({ ...route.data?.menu });
|
||||
} else {
|
||||
return this.dSpaceObjectDataService.findById(id, true, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((dsoRD) => {
|
||||
if (dsoRD.hasSucceeded) {
|
||||
const dso = dsoRD.payload;
|
||||
return combineLatest(this.getDsoMenus(dso, route, state)).pipe(
|
||||
// Menu sections are retrieved as an array of arrays and flattened into a single array
|
||||
map((combinedMenus) => [].concat.apply([], combinedMenus)),
|
||||
map((menus) => this.addDsoUuidToMenuIDs(menus, dso)),
|
||||
map((menus) => {
|
||||
return {
|
||||
...route.data?.menu,
|
||||
[MenuID.DSO_EDIT]: menus
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return observableOf({...route.data?.menu});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the menus for a dso based on the route and state
|
||||
*/
|
||||
getDsoMenus(dso, route, state): Observable<MenuSection[]>[] {
|
||||
return [
|
||||
this.getItemMenu(dso),
|
||||
this.getCommonMenu(dso, state)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common menus between all dspace objects
|
||||
*/
|
||||
protected getCommonMenu(dso, state): Observable<MenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
|
||||
]).pipe(
|
||||
map(([canEditItem]) => {
|
||||
return [
|
||||
{
|
||||
id: 'edit-dso',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: this.getDsoType(dso) + '.page.edit',
|
||||
link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString()
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 2
|
||||
},
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item specific menus
|
||||
*/
|
||||
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Item) {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||
]).pipe(
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem]) => {
|
||||
const isPerson = this.getDsoType(dso) === 'person';
|
||||
return [
|
||||
{
|
||||
id: 'orcid-dso',
|
||||
active: false,
|
||||
visible: isPerson && canSynchronizeWithOrcid,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'item.page.orcid.tooltip',
|
||||
link: new URLCombiner(getDSORoute(dso), 'orcid').toString()
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'orcid fab fa-lg',
|
||||
index: 0
|
||||
},
|
||||
{
|
||||
id: 'version-dso',
|
||||
active: false,
|
||||
visible: canCreateVersion,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: versionTooltip,
|
||||
disabled: disableVersioning,
|
||||
function: () => {
|
||||
this.dsoVersioningModalService.openCreateVersionModal(dso);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'code-branch',
|
||||
index: 1
|
||||
},
|
||||
{
|
||||
id: 'claim-dso',
|
||||
active: false,
|
||||
visible: isPerson && canClaimItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'item.page.claim.button',
|
||||
function: () => {
|
||||
this.claimResearcher(dso);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'hand-paper',
|
||||
index: 3
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a researcher by creating a profile
|
||||
* Shows notifications and/or hides the menu section on success/error
|
||||
*/
|
||||
protected claimResearcher(dso) {
|
||||
this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(dso.self)
|
||||
.subscribe((id: string) => {
|
||||
if (isNotEmpty(id)) {
|
||||
this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'),
|
||||
this.translate.get('researcherprofile.success.claim.body'));
|
||||
this.authorizationService.invalidateAuthorizationsRequestCache();
|
||||
this.menuService.hideMenuSection(MenuID.DSO_EDIT, 'claim-dso-' + dso.uuid);
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translate.get('researcherprofile.error.claim.title'),
|
||||
this.translate.get('researcherprofile.error.claim.body'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dso or entity type for an object to be used in generic messages
|
||||
*/
|
||||
protected getDsoType(dso) {
|
||||
const renderType = dso.getRenderTypes()[0];
|
||||
if (typeof renderType === 'string' || renderType instanceof String) {
|
||||
return renderType.toLowerCase();
|
||||
} else {
|
||||
return dso.type.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dso uuid to all provided menu ids and parent ids
|
||||
*/
|
||||
protected addDsoUuidToMenuIDs(menus, dso) {
|
||||
return menus.map((menu) => {
|
||||
Object.assign(menu, {
|
||||
id: menu.id + '-' + dso.uuid
|
||||
});
|
||||
if (hasValue(menu.parentID)) {
|
||||
Object.assign(menu, {
|
||||
parentID: menu.parentID + '-' + dso.uuid
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
});
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ import { Component } from '@angular/core';
|
||||
import { DsoEditMenuExpandableSectionComponent } from './dso-edit-menu-expandable-section.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MenuItemType } from 'src/app/shared/menu/menu-item-type.model';
|
||||
import { MenuItemModels } from '../../../menu/menu-section.model';
|
||||
|
||||
describe('DsoEditMenuExpandableSectionComponent', () => {
|
||||
let component: DsoEditMenuExpandableSectionComponent;
|
||||
@@ -30,6 +31,48 @@ describe('DsoEditMenuExpandableSectionComponent', () => {
|
||||
icon: iconString
|
||||
};
|
||||
|
||||
describe('when there are subsections', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
declarations: [DsoEditMenuExpandableSectionComponent, TestComponent],
|
||||
providers: [
|
||||
{provide: 'sectionDataProvider', useValue: dummySection},
|
||||
{provide: MenuService, useValue: menuService},
|
||||
{provide: CSSVariableService, useClass: CSSVariableServiceStub},
|
||||
{provide: Router, useValue: new RouterStub()},
|
||||
]
|
||||
}).overrideComponent(DsoEditMenuExpandableSectionComponent, {
|
||||
set: {
|
||||
entryComponents: [TestComponent]
|
||||
}
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([{
|
||||
id: 'test',
|
||||
visible: true,
|
||||
model: {} as MenuItemModels
|
||||
}]));
|
||||
fixture = TestBed.createComponent(DsoEditMenuExpandableSectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show a button with the icon', () => {
|
||||
const button = fixture.debugElement.query(By.css('.btn-dark'));
|
||||
expect(button.nativeElement.innerHTML).toContain('fa-' + iconString);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are no subsections', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
@@ -60,9 +103,10 @@ describe('DsoEditMenuExpandableSectionComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show a button with the icon', () => {
|
||||
it('should now show a button', () => {
|
||||
const button = fixture.debugElement.query(By.css('.btn-dark'));
|
||||
expect(button.nativeElement.innerHTML).toContain('fa-' + iconString);
|
||||
expect(button).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -10,7 +10,6 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati
|
||||
import { AuthService } from '../../../core/auth/auth.service';
|
||||
import { AuthServiceStub } from '../../testing/auth-service.stub';
|
||||
import { MenuService } from '../../menu/menu.service';
|
||||
import { MenuItemModel } from '../../menu/menu-item/models/menu-item.model';
|
||||
import { ThemeService } from '../../theme-support/theme.service';
|
||||
import { getMockThemeService } from '../../mocks/theme-service.mock';
|
||||
|
||||
|
@@ -1,8 +0,0 @@
|
||||
<button *ngIf="isAuthorized$ | async" data-test="subscription-button"
|
||||
(click)="openSubscriptionModal()"
|
||||
[ngbTooltip]="'subscriptions.tooltip' | translate"
|
||||
[title]="'subscriptions.tooltip' | translate"
|
||||
[attr.aria-label]="'subscriptions.tooltip' | translate"
|
||||
class="subscription-button btn btn-dark btn-sm">
|
||||
<i class="fas fa-bell fa-fw"></i>
|
||||
</button>
|
@@ -1,83 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DsoPageSubscriptionButtonComponent } from './dso-page-subscription-button.component';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ITEM } from '../../../core/shared/item.resource-type';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateLoaderMock } from '../../mocks/translate-loader.mock';
|
||||
|
||||
describe('DsoPageSubscriptionButtonComponent', () => {
|
||||
let component: DsoPageSubscriptionButtonComponent;
|
||||
let fixture: ComponentFixture<DsoPageSubscriptionButtonComponent>;
|
||||
let de: DebugElement;
|
||||
|
||||
const authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: jasmine.createSpy('isAuthorized') // observableOf(true)
|
||||
});
|
||||
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: 'fake-id',
|
||||
uuid: 'fake-id',
|
||||
handle: 'fake/handle',
|
||||
lastModified: '2018',
|
||||
type: ITEM,
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://localhost:8000/items/fake-id'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
NgbModalModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [ DsoPageSubscriptionButtonComponent ],
|
||||
providers: [
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DsoPageSubscriptionButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
de = fixture.debugElement;
|
||||
component.dso = mockItem;
|
||||
});
|
||||
|
||||
describe('when is authorized', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized.and.returnValue(observableOf(true));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display subscription button', () => {
|
||||
expect(de.query(By.css(' [data-test="subscription-button"]'))).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when is not authorized', () => {
|
||||
beforeEach(() => {
|
||||
authorizationService.isAuthorized.and.returnValue(observableOf(false));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not display subscription button', () => {
|
||||
expect(de.query(By.css(' [data-test="subscription-button"]'))).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,57 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { SubscriptionModalComponent } from '../../subscriptions/subscription-modal/subscription-modal.component';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dso-page-subscription-button',
|
||||
templateUrl: './dso-page-subscription-button.component.html',
|
||||
styleUrls: ['./dso-page-subscription-button.component.scss']
|
||||
})
|
||||
/**
|
||||
* Display a button that opens the modal to manage subscriptions
|
||||
*/
|
||||
export class DsoPageSubscriptionButtonComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* Whether the current user is authorized to edit the DSpaceObject
|
||||
*/
|
||||
isAuthorized$: Observable<boolean> = of(false);
|
||||
|
||||
/**
|
||||
* Reference to NgbModal
|
||||
*/
|
||||
public modalRef: NgbModalRef;
|
||||
|
||||
/**
|
||||
* DSpaceObject that is being viewed
|
||||
*/
|
||||
@Input() dso: DSpaceObject;
|
||||
|
||||
constructor(
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
private modalService: NgbModal,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current DSpaceObject can be subscribed by the user
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanSubscribe, this.dso.self);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the modal to subscribe to the related DSpaceObject
|
||||
*/
|
||||
public openSubscriptionModal() {
|
||||
this.modalRef = this.modalService.open(SubscriptionModalComponent);
|
||||
this.modalRef.componentInstance.dso = this.dso;
|
||||
}
|
||||
|
||||
}
|
@@ -7,13 +7,15 @@
|
||||
*/
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router';
|
||||
import flatten from 'lodash/flatten';
|
||||
import { combineLatest, Observable, } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable, } from 'rxjs';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { MenuItemModels } from './menu-section.model';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Partial menu section
|
||||
* This object acts like a menu section but with certain properties being optional
|
||||
*/
|
||||
export interface PartialMenuSection {
|
||||
id?: string;
|
||||
visible: boolean;
|
||||
@@ -26,15 +28,28 @@ export interface PartialMenuSection {
|
||||
alwaysRenderExpandable?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface to represent a menu provider
|
||||
* Implementations of this provider will contain sections to be added to the menus
|
||||
*/
|
||||
export interface MenuProvider {
|
||||
shouldPersistOnRouteChange?: boolean,
|
||||
menuID?: MenuID;
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* Retrieve the sections from the provider. These sections can be route dependent.
|
||||
* @param route - The route on which the menu sections possibly depend
|
||||
* @param state - The router snapshot on which the sections possibly depend
|
||||
*/
|
||||
getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to represent a Menu Provider together with additional information added through the static methods on
|
||||
* AbstractMenuProvider. This additional information is either the paths on which the sections of this provider should
|
||||
* be present or a list of child providers
|
||||
*/
|
||||
export class MenuProviderTypeWithOptions {
|
||||
providerType: Type<MenuProvider>;
|
||||
paths?: string[];
|
||||
@@ -42,6 +57,9 @@ export class MenuProviderTypeWithOptions {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class to be extended when creating menu providers
|
||||
*/
|
||||
export abstract class AbstractMenuProvider implements MenuProvider {
|
||||
|
||||
/**
|
||||
@@ -54,17 +72,44 @@ export abstract class AbstractMenuProvider implements MenuProvider {
|
||||
* Whether the sections of this menu should be set on the
|
||||
*/
|
||||
shouldPersistOnRouteChange = true;
|
||||
|
||||
/**
|
||||
* The ID of the menu provider.
|
||||
* This will be automatically set based on the menu and the index of the provider in the list
|
||||
*/
|
||||
menuProviderId?: string;
|
||||
|
||||
/**
|
||||
* The index of the menu provider
|
||||
* This will be automatically set based on the index of the provider in the list
|
||||
*/
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* The paths on which the sections of this provider will be active
|
||||
* This will be automatically set based on the paths added based on the paths provided through the 'onRoute' static
|
||||
* method in the app.menus.ts file
|
||||
*/
|
||||
activePaths?: string[];
|
||||
|
||||
/**
|
||||
* The ID of the parent provider of this provider.
|
||||
* This will be automatically set based on the provider that calls the 'withSubs' static method with this provider
|
||||
* in the list of arguments
|
||||
*/
|
||||
parentID?: string;
|
||||
|
||||
/**
|
||||
* Whether the menu section or top section of this provider will always be rendered as expandable and hidden when no children are present
|
||||
* When true, the sections added by this provider will be assumed to be parent sections with children
|
||||
* The sections will not be rendered when they have no visible children
|
||||
* This can be overwritten on the level of sections
|
||||
*/
|
||||
alwaysRenderExpandable? = false;
|
||||
|
||||
|
||||
/**
|
||||
* Static method to be called from the app.menus.ts file to define paths on which this provider should the active
|
||||
* @param paths - The paths on which the sections of this provider should be active
|
||||
*/
|
||||
public static onRoute(...paths: string[]): MenuProviderTypeWithOptions {
|
||||
if (!AbstractMenuProvider.isPrototypeOf(this)) {
|
||||
throw new Error(
|
||||
@@ -77,7 +122,7 @@ export abstract class AbstractMenuProvider implements MenuProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to add sub menu providers to this top provider
|
||||
* Static method to be called from the app.menus.ts file to add sub menu providers to this top provider
|
||||
* @param childProviders - the list of sub providers that will provide subsections for this provider
|
||||
*/
|
||||
public static withSubs(childProviders: (Type<MenuProvider> | MenuProviderTypeWithOptions)[]): MenuProviderTypeWithOptions {
|
||||
@@ -91,13 +136,13 @@ export abstract class AbstractMenuProvider implements MenuProvider {
|
||||
return {providerType: providerType, childProviderTypes: childProviders};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sections from the provider. These sections can be route dependent.
|
||||
* @param route - The route on which the menu sections possibly depend
|
||||
* @param state - The router snapshot on which the sections possibly depend
|
||||
*/
|
||||
abstract getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot): Observable<PartialMenuSection[]>;
|
||||
|
||||
protected concat(...sections$: Observable<PartialMenuSection[]>[]): Observable<PartialMenuSection[]> {
|
||||
return combineLatest(sections$).pipe(
|
||||
map(sections => flatten(sections)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
165
src/app/shared/menu/menu-provider.service.spec.ts
Normal file
165
src/app/shared/menu/menu-provider.service.spec.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { AbstractMenuProvider, PartialMenuSection } from './menu-provider.model';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { ActivatedRouteSnapshot, ResolveEnd, RouterStateSnapshot, UrlSegment } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { MenuItemType } from './menu-item-type.model';
|
||||
import { waitForAsync } from '@angular/core/testing';
|
||||
import { MenuProviderService } from './menu-provider.service';
|
||||
import { MenuService } from './menu.service';
|
||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||
|
||||
describe('MenuProviderService', () => {
|
||||
|
||||
class TestMenuProvider extends AbstractMenuProvider {
|
||||
|
||||
constructor(
|
||||
public menuID: MenuID,
|
||||
public shouldPersistOnRouteChange: boolean,
|
||||
public menuProviderId: string,
|
||||
public index: number,
|
||||
public activePaths: string[],
|
||||
public parentID: string,
|
||||
public alwaysRenderExpandable: boolean,
|
||||
public sections: PartialMenuSection[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getSections(route?: ActivatedRouteSnapshot, state?: RouterStateSnapshot) {
|
||||
return observableOf(this.sections);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let menuProviderService: MenuProviderService;
|
||||
let menuService: MenuService;
|
||||
|
||||
const router = {
|
||||
events: observableOf(new ResolveEnd(1, 'test-url', 'test-url-after-redirect',{url: 'test-url', root: {url: [new UrlSegment('test-url', {})]}} as any ))
|
||||
};
|
||||
|
||||
const section = {
|
||||
visible: true, model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: `test1`,
|
||||
},
|
||||
};
|
||||
|
||||
const sectionToBeRemoved = {
|
||||
id: 'sectionToBeRemoved',
|
||||
visible: true, model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: `test1`,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const persistentProvider1 = new TestMenuProvider(MenuID.PUBLIC, true, 'provider1', 0, undefined, undefined, false, [section]);
|
||||
const persistentProvider2 = new TestMenuProvider(MenuID.PUBLIC, true, 'provider2', 1, undefined, 'provider1', false, [section]);
|
||||
const nonPersistentProvider3 = new TestMenuProvider(MenuID.PUBLIC, false, 'provider3', 2, undefined, undefined, false, [section]);
|
||||
const nonPersistentProvider4 = new TestMenuProvider(MenuID.PUBLIC, false, 'provider4', 3, undefined, 'provider3', false, [section]);
|
||||
const nonPersistentProvider5WithRoutes = new TestMenuProvider(MenuID.PUBLIC, false, 'provider4', 3, [COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH], undefined, false, [section]);
|
||||
|
||||
const listOfProvider = [persistentProvider1, persistentProvider2, nonPersistentProvider3, nonPersistentProvider4, nonPersistentProvider5WithRoutes];
|
||||
|
||||
const expectedSection1 = generateAddedSection(persistentProvider1, section);
|
||||
const expectedSection2 = generateAddedSection(persistentProvider2, section);
|
||||
const expectedSection3 = generateAddedSection(nonPersistentProvider3, section);
|
||||
const expectedSection4 = generateAddedSection(nonPersistentProvider4, section);
|
||||
const expectedSection5 = generateAddedSection(nonPersistentProvider5WithRoutes, section);
|
||||
|
||||
function generateAddedSection(provider, sectionToAdd) {
|
||||
return {
|
||||
...sectionToAdd,
|
||||
id: sectionToAdd.id ?? `${provider.menuProviderId}`,
|
||||
parentID: sectionToAdd.parentID ?? provider.parentID,
|
||||
index: sectionToAdd.index ?? provider.index,
|
||||
shouldPersistOnRouteChange: sectionToAdd.shouldPersistOnRouteChange ?? provider.shouldPersistOnRouteChange,
|
||||
alwaysRenderExpandable: sectionToAdd.alwaysRenderExpandable ?? provider.alwaysRenderExpandable,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
|
||||
menuService = jasmine.createSpyObj('MenuService',
|
||||
{
|
||||
addSection: {},
|
||||
removeSection: {},
|
||||
getMenu: observableOf({id: MenuID.PUBLIC}),
|
||||
getNonPersistentMenuSections: observableOf([sectionToBeRemoved])
|
||||
|
||||
});
|
||||
|
||||
menuProviderService = new MenuProviderService(listOfProvider, menuService, router as any);
|
||||
|
||||
}));
|
||||
|
||||
describe('initPersistentMenus', () => {
|
||||
it('should initialise the menu sections from the persistent providers', () => {
|
||||
menuProviderService.initPersistentMenus();
|
||||
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection2);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(nonPersistentProvider3.menuID, expectedSection3);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(nonPersistentProvider4.menuID, expectedSection4);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(nonPersistentProvider5WithRoutes.menuID, expectedSection5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRouteMenus with no matching path specific providers', () => {
|
||||
it('should remove the current non persistent menus and add the general non persistent menus', () => {
|
||||
const route = {};
|
||||
const state = {url: 'test-url'};
|
||||
menuProviderService.resolveRouteMenus(route as any, state as any).subscribe();
|
||||
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id);
|
||||
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection2);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider3.menuID, expectedSection3);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider4.menuID, expectedSection4);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(nonPersistentProvider5WithRoutes.menuID, expectedSection5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRouteMenus with a matching path specific provider', () => {
|
||||
it('should remove the current non persistent menus and add the general non persistent menus', () => {
|
||||
const route = {};
|
||||
const state = {url: `xxxx/${COMMUNITY_MODULE_PATH}/xxxxxx`};
|
||||
menuProviderService.resolveRouteMenus(route as any, state as any).subscribe();
|
||||
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id);
|
||||
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection2);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider3.menuID, expectedSection3);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider4.menuID, expectedSection4);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider5WithRoutes.menuID, expectedSection5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listenForRouteChanges ', () => {
|
||||
it('should listen to the route changes and update the menu sections based on the retrieved state and route', () => {
|
||||
menuProviderService.listenForRouteChanges();
|
||||
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id);
|
||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id);
|
||||
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection2);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider3.menuID, expectedSection3);
|
||||
expect(menuService.addSection).toHaveBeenCalledWith(nonPersistentProvider4.menuID, expectedSection4);
|
||||
expect(menuService.addSection).not.toHaveBeenCalledWith(nonPersistentProvider5WithRoutes.menuID, expectedSection5);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@@ -6,17 +6,21 @@
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, Injector, Optional, } from '@angular/core';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, ResolveEnd, Router, RouterStateSnapshot, } from '@angular/router';
|
||||
import { Inject, Injectable, Optional, } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, ResolveEnd, Router, RouterStateSnapshot, } from '@angular/router';
|
||||
import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { filter, find, switchMap, take, } from 'rxjs/operators';
|
||||
import { hasValue, isNotEmpty } from '../empty.util';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection } from './menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection } from './menu-provider.model';
|
||||
import { MenuState } from './menu-state.model';
|
||||
import { MenuService } from './menu.service';
|
||||
import { MENU_PROVIDER } from './menu.structure';
|
||||
|
||||
/**
|
||||
* Service that is responsible for adding and removing the menu sections created by the providers, both for
|
||||
* persistent and non-persistent menu sections
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@@ -24,9 +28,7 @@ export class MenuProviderService {
|
||||
constructor(
|
||||
@Inject(MENU_PROVIDER) @Optional() protected providers: ReadonlyArray<AbstractMenuProvider>,
|
||||
protected menuService: MenuService,
|
||||
protected injector: Injector,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -42,22 +44,22 @@ export class MenuProviderService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for route changes and resolve the route dependent menu sections on route change
|
||||
*/
|
||||
listenForRouteChanges() {
|
||||
this.router.events.pipe(
|
||||
filter(event => event instanceof ResolveEnd),
|
||||
switchMap((event: ResolveEnd) => {
|
||||
|
||||
const currentRoute = this.getCurrentRoute(event.state.root);
|
||||
|
||||
return this.resolveRouteMenus(currentRoute, event.state);
|
||||
}),
|
||||
).subscribe((done) => {
|
||||
Object.values(MenuID).forEach((menuID) => {
|
||||
this.menuService.buildRouteMenuSections(menuID);
|
||||
});
|
||||
});
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full current route
|
||||
*/
|
||||
private getCurrentRoute(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
|
||||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
@@ -66,6 +68,9 @@ export class MenuProviderService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the persistent menu sections
|
||||
*/
|
||||
public initPersistentMenus() {
|
||||
combineLatest([
|
||||
...this.providers
|
||||
@@ -87,20 +92,22 @@ export class MenuProviderService {
|
||||
sections: PartialMenuSection[]
|
||||
}, sectionIndex) => {
|
||||
providerWithSection.sections.forEach((section) => {
|
||||
this.addSection(providerWithSection, section);
|
||||
this.addSection(providerWithSection.provider, section);
|
||||
});
|
||||
return this.waitForMenu$(providerWithSection.provider.menuID);
|
||||
});
|
||||
return [waitForMenus];
|
||||
}),
|
||||
map(done => done.every(Boolean)),
|
||||
).subscribe((done) => {
|
||||
Object.values(MenuID).forEach((menuID) => {
|
||||
this.menuService.buildRouteMenuSections(menuID);
|
||||
});
|
||||
});
|
||||
take(1),
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the non-persistent route based menu sections
|
||||
* @param route - the current route
|
||||
* @param state - the current router state
|
||||
*/
|
||||
public resolveRouteMenus(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
@@ -149,7 +156,7 @@ export class MenuProviderService {
|
||||
sections: PartialMenuSection[]
|
||||
}) => {
|
||||
providerWithSection.sections.forEach((section) => {
|
||||
this.addSection(providerWithSection, section);
|
||||
this.addSection(providerWithSection.provider, section);
|
||||
});
|
||||
return this.waitForMenu$(providerWithSection.provider.menuID);
|
||||
});
|
||||
@@ -159,22 +166,28 @@ export class MenuProviderService {
|
||||
);
|
||||
}
|
||||
|
||||
private addSection(providerWithSection: {
|
||||
provider: AbstractMenuProvider;
|
||||
sections: PartialMenuSection[]
|
||||
}, section: PartialMenuSection) {
|
||||
this.menuService.addSection(providerWithSection.provider.menuID, {
|
||||
/**
|
||||
* Add the provided section combined with information from the menu provider to the menus
|
||||
* @param provider - The provider of the section which will be used to provide extra data to the section
|
||||
* @param section - The partial section to be added to the menus
|
||||
*/
|
||||
private addSection(provider: AbstractMenuProvider, section: PartialMenuSection) {
|
||||
this.menuService.addSection(provider.menuID, {
|
||||
...section,
|
||||
id: section.id ?? `${providerWithSection.provider.menuProviderId}`,
|
||||
parentID: section.parentID ?? providerWithSection.provider.parentID,
|
||||
index: section.index ?? providerWithSection.provider.index,
|
||||
shouldPersistOnRouteChange: section.shouldPersistOnRouteChange ?? providerWithSection.provider.shouldPersistOnRouteChange,
|
||||
alwaysRenderExpandable: section.alwaysRenderExpandable ?? providerWithSection.provider.alwaysRenderExpandable,
|
||||
id: section.id ?? `${provider.menuProviderId}`,
|
||||
parentID: section.parentID ?? provider.parentID,
|
||||
index: section.index ?? provider.index,
|
||||
shouldPersistOnRouteChange: section.shouldPersistOnRouteChange ?? provider.shouldPersistOnRouteChange,
|
||||
alwaysRenderExpandable: section.alwaysRenderExpandable ?? provider.alwaysRenderExpandable,
|
||||
});
|
||||
}
|
||||
|
||||
private removeNonPersistentSections(menuSectionsPerMenu) {
|
||||
menuSectionsPerMenu.forEach((menu) => {
|
||||
/**
|
||||
* Remove all non-persistent sections from the menus
|
||||
* @param menuWithSections - The menu with its sections to be removed
|
||||
*/
|
||||
private removeNonPersistentSections(menuWithSections) {
|
||||
menuWithSections.forEach((menu) => {
|
||||
menu.sections.forEach((section) => {
|
||||
this.menuService.removeSection(menu.menuId, section.id);
|
||||
});
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { MenuItemType } from './menu-item-type.model';
|
||||
import { AltmetricMenuItemModel } from './menu-item/models/altmetric.model';
|
||||
import { ExternalLinkMenuItemModel } from './menu-item/models/external-link.model';
|
||||
import { LinkMenuItemModel } from './menu-item/models/link.model';
|
||||
@@ -14,26 +13,6 @@ export type MenuItemModels =
|
||||
| SearchMenuItemModel
|
||||
| TextMenuItemModel;
|
||||
|
||||
function itemModelFactory(type: MenuItemType): MenuItemModels {
|
||||
switch (type) {
|
||||
case MenuItemType.TEXT:
|
||||
return new TextMenuItemModel();
|
||||
case MenuItemType.LINK:
|
||||
return new LinkMenuItemModel();
|
||||
case MenuItemType.ALTMETRIC:
|
||||
return new AltmetricMenuItemModel();
|
||||
case MenuItemType.SEARCH:
|
||||
return new SearchMenuItemModel();
|
||||
case MenuItemType.ONCLICK:
|
||||
return new OnClickMenuItemModel();
|
||||
case MenuItemType.EXTERNAL:
|
||||
return new ExternalLinkMenuItemModel();
|
||||
default: {
|
||||
throw new Error(`No such menu item type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface MenuSection {
|
||||
/**
|
||||
* The identifier for this section
|
||||
@@ -80,5 +59,9 @@ export interface MenuSection {
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* When true, the current section will be assumed to be a parent section with children
|
||||
* This section will not be rendered when it has no visible children
|
||||
*/
|
||||
alwaysRenderExpandable?: boolean;
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ describe('MenuComponent', () => {
|
||||
} as TextMenuItemModel,
|
||||
icon: 'globe',
|
||||
visible: true,
|
||||
}
|
||||
};
|
||||
|
||||
const mockMenuID = 'mock-menuID' as MenuID;
|
||||
|
||||
|
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* The contents of this file are subject to the license and copyright
|
||||
* detailed in the LICENSE and NOTICE files at the root of the source
|
||||
* tree and available online at
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export function resolveStaticMenus(): (ActivatedRouteSnapshot, RouterStateSnapshot, ProviderMenuService) => Observable<boolean> {
|
||||
// return (
|
||||
// route: ActivatedRouteSnapshot,
|
||||
// state: RouterStateSnapshot,
|
||||
// menuProviderService: MenuProviderService = inject(MenuProviderService),
|
||||
// ) => menuProviderService.resolveStaticMenu();
|
||||
// }
|
@@ -39,9 +39,7 @@ describe('MenuService', () => {
|
||||
let topSections;
|
||||
let initialState;
|
||||
let routeDataMenuSection: MenuSection;
|
||||
let routeDataMenuSectionResolved: MenuSection;
|
||||
let routeDataMenuChildSection: MenuSection;
|
||||
let toBeRemovedMenuSection: MenuSection;
|
||||
let alreadyPresentMenuSection: MenuSection;
|
||||
let route;
|
||||
let router;
|
||||
@@ -106,16 +104,6 @@ describe('MenuService', () => {
|
||||
link: 'path/:linkparam'
|
||||
} as LinkMenuItemModel
|
||||
};
|
||||
routeDataMenuSectionResolved = {
|
||||
id: 'mockSection_id_param_resolved',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.mockSection',
|
||||
link: 'path/link_param_resolved'
|
||||
} as LinkMenuItemModel
|
||||
};
|
||||
routeDataMenuChildSection = {
|
||||
id: 'mockChildSection',
|
||||
parentID: 'mockSection',
|
||||
@@ -127,16 +115,6 @@ describe('MenuService', () => {
|
||||
link: ''
|
||||
} as LinkMenuItemModel
|
||||
};
|
||||
toBeRemovedMenuSection = {
|
||||
id: 'toBeRemovedSection',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.toBeRemovedSection',
|
||||
link: ''
|
||||
} as LinkMenuItemModel
|
||||
};
|
||||
alreadyPresentMenuSection = {
|
||||
id: 'alreadyPresentSection',
|
||||
active: false,
|
||||
@@ -539,69 +517,4 @@ describe('MenuService', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new DeactivateMenuSectionAction(MenuID.ADMIN, 'fakeID'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildRouteMenuSections', () => {
|
||||
it('should add and remove menu sections depending on the current route', () => {
|
||||
spyOn(service, 'addSection');
|
||||
spyOn(service, 'removeSection');
|
||||
|
||||
spyOn(service, 'getNonPersistentMenuSections').and.returnValue(observableOf([toBeRemovedMenuSection, alreadyPresentMenuSection]));
|
||||
|
||||
service.buildRouteMenuSections(MenuID.PUBLIC);
|
||||
|
||||
expect(service.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuSectionResolved);
|
||||
expect(service.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuChildSection);
|
||||
expect(service.addSection).not.toHaveBeenCalledWith(MenuID.PUBLIC, alreadyPresentMenuSection);
|
||||
expect(service.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, toBeRemovedMenuSection.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listenForRouteChanges', () => {
|
||||
it('should build the menu sections on NavigationEnd event', () => {
|
||||
spyOn(service, 'buildRouteMenuSections');
|
||||
|
||||
service.listenForRouteChanges();
|
||||
|
||||
expect(service.buildRouteMenuSections).toHaveBeenCalledWith(MenuID.ADMIN);
|
||||
expect(service.buildRouteMenuSections).toHaveBeenCalledWith(MenuID.PUBLIC);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`resolveSubstitutions`, () => {
|
||||
let linkPrefix;
|
||||
let link;
|
||||
let uuid;
|
||||
|
||||
beforeEach(() => {
|
||||
linkPrefix = 'statistics_collection_';
|
||||
link = `${linkPrefix}:id`;
|
||||
uuid = 'f7cc3ca4-3c2c-464d-8af8-add9f84f711c';
|
||||
});
|
||||
|
||||
it(`shouldn't do anything when there are no params`, () => {
|
||||
let result = (service as any).resolveSubstitutions(link, undefined);
|
||||
expect(result).toEqual(link);
|
||||
result = (service as any).resolveSubstitutions(link, null);
|
||||
expect(result).toEqual(link);
|
||||
result = (service as any).resolveSubstitutions(link, {});
|
||||
expect(result).toEqual(link);
|
||||
});
|
||||
|
||||
it(`should replace link params that are also route params`, () => {
|
||||
const result = (service as any).resolveSubstitutions(link,{ 'id': uuid });
|
||||
expect(result).toEqual(linkPrefix + uuid);
|
||||
});
|
||||
|
||||
it(`should not replace link params that aren't route params`, () => {
|
||||
const result = (service as any).resolveSubstitutions(link,{ 'something': 'else' });
|
||||
expect(result).toEqual(link);
|
||||
});
|
||||
|
||||
it(`should gracefully deal with routes that contain the name of the route param`, () => {
|
||||
const selfReferentialParam = `:id:something`;
|
||||
const result = (service as any).resolveSubstitutions(link,{ 'id': selfReferentialParam });
|
||||
expect(result).toEqual(linkPrefix + selfReferentialParam);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { AppState, keySelector } from '../../app.reducer';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
ActivateMenuSectionAction,
|
||||
AddMenuSectionAction,
|
||||
@@ -17,12 +17,12 @@ import {
|
||||
ToggleActiveMenuSectionAction,
|
||||
ToggleMenuAction,
|
||||
} from './menu.actions';
|
||||
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty, isEmpty } from '../empty.util';
|
||||
import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../empty.util';
|
||||
import { MenuState } from './menu-state.model';
|
||||
import { MenuSections } from './menu-sections.model';
|
||||
import { MenuSection } from './menu-section.model';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
export function menuKeySelector<T>(key: string, selector): MemoizedSelector<MenuState, T> {
|
||||
return createSelector(selector, (state) => {
|
||||
@@ -344,95 +344,4 @@ export class MenuService {
|
||||
return this.getMenuSection(menuID, id).pipe(map((section) => section.visible));
|
||||
}
|
||||
|
||||
listenForRouteChanges(): void {
|
||||
this.router.events.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
).subscribe(() => {
|
||||
Object.values(MenuID).forEach((menuID) => {
|
||||
this.buildRouteMenuSections(menuID);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build menu sections depending on the current route
|
||||
* - Adds sections found in the current route data that aren't active yet
|
||||
* - Removes sections that are active, but not present in the current route data
|
||||
* @param menuID The menu to add/remove sections to/from
|
||||
*/
|
||||
buildRouteMenuSections(menuID: MenuID) {
|
||||
this.getNonPersistentMenuSections(menuID).pipe(
|
||||
map((sections) => sections.map((section) => section.id)),
|
||||
take(1)
|
||||
).subscribe((shouldNotPersistIDs: string[]) => {
|
||||
const resolvedSections = this.resolveRouteMenuSections(this.route.root, menuID);
|
||||
resolvedSections.forEach((section) => {
|
||||
const index = shouldNotPersistIDs.indexOf(section.id);
|
||||
if (index > -1) {
|
||||
shouldNotPersistIDs.splice(index, 1);
|
||||
} else {
|
||||
this.addSection(menuID, section);
|
||||
}
|
||||
});
|
||||
shouldNotPersistIDs.forEach((id) => {
|
||||
|
||||
// this.removeSection(menuID, id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve menu sections defined in the current route data (including parent routes)
|
||||
* @param route The route to resolve data for
|
||||
* @param menuID The menu to resolve data for
|
||||
*/
|
||||
resolveRouteMenuSections(route: ActivatedRoute, menuID: MenuID): MenuSection[] {
|
||||
const data = route.snapshot.data;
|
||||
const params = route.snapshot.params;
|
||||
const last: boolean = hasNoValue(route.firstChild);
|
||||
|
||||
if (hasValue(data) && hasValue(data.menu) && hasValue(data.menu[menuID])) {
|
||||
let menuSections: MenuSection[] | MenuSection = data.menu[menuID];
|
||||
menuSections = this.resolveSubstitutions(menuSections, params);
|
||||
|
||||
if (!Array.isArray(menuSections)) {
|
||||
menuSections = [menuSections];
|
||||
}
|
||||
|
||||
if (!last) {
|
||||
return [...menuSections, ...this.resolveRouteMenuSections(route.firstChild, menuID)];
|
||||
} else {
|
||||
return [...menuSections];
|
||||
}
|
||||
}
|
||||
|
||||
return !last ? this.resolveRouteMenuSections(route.firstChild, menuID) : [];
|
||||
}
|
||||
|
||||
protected resolveSubstitutions(object, params) {
|
||||
let resolved;
|
||||
if (isEmpty(params)) {
|
||||
resolved = object;
|
||||
} else if (typeof object === 'string') {
|
||||
resolved = object;
|
||||
Object.entries(params).forEach(([key, value]: [string, string]) =>
|
||||
resolved = resolved.replaceAll(`:${key}`, value)
|
||||
);
|
||||
} else if (Array.isArray(object)) {
|
||||
resolved = [];
|
||||
object.forEach((entry, index) => {
|
||||
resolved[index] = this.resolveSubstitutions(object[index], params);
|
||||
});
|
||||
} else if (typeof object === 'object') {
|
||||
resolved = {};
|
||||
Object.keys(object).forEach((key) => {
|
||||
resolved[key] = this.resolveSubstitutions(object[key], params);
|
||||
});
|
||||
} else {
|
||||
resolved = object;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
102
src/app/shared/menu/menu.structure.spec.ts
Normal file
102
src/app/shared/menu/menu.structure.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { CommunityListMenuProvider } from './providers/community-list.menu';
|
||||
import { NewMenuProvider } from './providers/new.menu';
|
||||
import { DsoOptionMenu } from './providers/dso-option.menu';
|
||||
import { SubscribeMenuProvider } from './providers/comcol-subscribe.menu';
|
||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||
import { buildMenuStructure } from './menu.structure';
|
||||
import { MenuProviderService } from './menu-provider.service';
|
||||
import { BrowseMenuProvider } from './providers/browse.menu';
|
||||
import { StatisticsMenuProvider } from './providers/statistics.menu';
|
||||
import { EditMenuProvider } from './providers/edit.menu';
|
||||
import { ImportMenuProvider } from './providers/import.menu';
|
||||
import { ExportMenuProvider } from './providers/export.menu';
|
||||
import { AccessControlMenuProvider } from './providers/access-control.menu';
|
||||
import { AdminSearchMenuProvider } from './providers/admin-search.menu';
|
||||
import { RegistriesMenuProvider } from './providers/registries.menu';
|
||||
import { CurationMenuProvider } from './providers/curation.menu';
|
||||
import { ProcessesMenuProvider } from './providers/processes.menu';
|
||||
import { WorkflowMenuProvider } from './providers/workflow.menu';
|
||||
import { HealthMenuProvider } from './providers/health.menu';
|
||||
import { SystemWideAlertMenuProvider } from './providers/system-wide-alert.menu';
|
||||
import { DSpaceObjectEditMenuProvider } from './providers/dso-edit.menu';
|
||||
import { ENTITY_MODULE_PATH, ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
|
||||
import { VersioningMenuProvider } from './providers/item-versioning.menu';
|
||||
import { OrcidMenuProvider } from './providers/item-orcid.menu';
|
||||
import { ClaimMenuProvider } from './providers/item-claim.menu';
|
||||
|
||||
describe('buildMenuStructure', () => {
|
||||
const providerStructure =
|
||||
{
|
||||
[MenuID.PUBLIC]: [
|
||||
CommunityListMenuProvider,
|
||||
BrowseMenuProvider,
|
||||
StatisticsMenuProvider,
|
||||
],
|
||||
[MenuID.ADMIN]: [
|
||||
NewMenuProvider,
|
||||
EditMenuProvider,
|
||||
ImportMenuProvider,
|
||||
ExportMenuProvider,
|
||||
AccessControlMenuProvider,
|
||||
AdminSearchMenuProvider,
|
||||
RegistriesMenuProvider,
|
||||
CurationMenuProvider,
|
||||
ProcessesMenuProvider,
|
||||
WorkflowMenuProvider,
|
||||
HealthMenuProvider,
|
||||
SystemWideAlertMenuProvider,
|
||||
],
|
||||
[MenuID.DSO_EDIT]: [
|
||||
DsoOptionMenu.withSubs([
|
||||
SubscribeMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH),
|
||||
DSpaceObjectEditMenuProvider.onRoute(COMMUNITY_MODULE_PATH, COLLECTION_MODULE_PATH, ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||
VersioningMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||
OrcidMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH),
|
||||
ClaimMenuProvider.onRoute(ITEM_MODULE_PATH, ENTITY_MODULE_PATH, COLLECTION_MODULE_PATH),
|
||||
]),
|
||||
],
|
||||
};
|
||||
|
||||
const orderedProviderTypeList =
|
||||
[
|
||||
CommunityListMenuProvider,
|
||||
BrowseMenuProvider,
|
||||
StatisticsMenuProvider,
|
||||
NewMenuProvider,
|
||||
EditMenuProvider,
|
||||
ImportMenuProvider,
|
||||
ExportMenuProvider,
|
||||
AccessControlMenuProvider,
|
||||
AdminSearchMenuProvider,
|
||||
RegistriesMenuProvider,
|
||||
CurationMenuProvider,
|
||||
ProcessesMenuProvider,
|
||||
WorkflowMenuProvider,
|
||||
HealthMenuProvider,
|
||||
SystemWideAlertMenuProvider,
|
||||
SubscribeMenuProvider,
|
||||
DSpaceObjectEditMenuProvider,
|
||||
VersioningMenuProvider,
|
||||
OrcidMenuProvider,
|
||||
ClaimMenuProvider,
|
||||
DsoOptionMenu,
|
||||
];
|
||||
|
||||
|
||||
it('should have a double amount of objects with an additional service after the processing', () => {
|
||||
const result = buildMenuStructure(providerStructure);
|
||||
expect(result.length).toEqual(orderedProviderTypeList.length * 2 + 1);
|
||||
});
|
||||
|
||||
it('should return a list with the MenuProviderService and then a resolved provider and provider type for each provider in the provided structure', () => {
|
||||
const result = buildMenuStructure(providerStructure);
|
||||
expect(result[0]).toEqual(MenuProviderService);
|
||||
|
||||
orderedProviderTypeList.forEach((provider, index) => {
|
||||
expect((result[(index + 1) * 2 - 1] as any).deps).toEqual([provider]);
|
||||
expect(result[(index + 1) * 2]).toEqual(provider);
|
||||
});
|
||||
});
|
||||
});
|
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import { InjectionToken, Provider, Type, } from '@angular/core';
|
||||
import { MenuID } from './menu-id.model';
|
||||
import { AbstractMenuProvider, MenuProviderTypeWithOptions } from './menu-provider';
|
||||
import { AbstractMenuProvider, MenuProviderTypeWithOptions } from './menu-provider.model';
|
||||
import { MenuProviderService } from './menu-provider.service';
|
||||
import { hasValue, isNotEmpty } from '../empty.util';
|
||||
|
||||
@@ -52,7 +52,7 @@ function processProviderType(providers: Provider[], menuID: string, providerType
|
||||
const childProviderTypes = (providerType as any).childProviderTypes;
|
||||
|
||||
childProviderTypes.forEach((childProviderType, childIndex: number) => {
|
||||
processProviderType(providers, menuID, childProviderType, childIndex, `${providerPart.name}`, hasSubProviders);
|
||||
processProviderType(providers, menuID, childProviderType, childIndex, `${menuID}_${index}`, hasSubProviders);
|
||||
});
|
||||
processProviderType(providers, menuID, providerPart, index, parentID, true);
|
||||
|
||||
@@ -83,10 +83,10 @@ function addProviderToList(providers: Provider[], providerType: Type<AbstractMen
|
||||
provider.menuID = menuID as MenuID;
|
||||
provider.index = provider.index ?? index;
|
||||
if (hasValue(parentID)) {
|
||||
provider.menuProviderId = `${parentID}_${provider.constructor.name}`;
|
||||
provider.parentID = parentID;
|
||||
provider.menuProviderId = provider.menuProviderId ?? `${parentID}_${index}`;
|
||||
provider.parentID = provider.parentID ?? parentID;
|
||||
} else {
|
||||
provider.menuProviderId = `${provider.constructor.name}`;
|
||||
provider.menuProviderId = provider.menuProviderId ?? `${menuID}_${index}`;
|
||||
}
|
||||
if (isNotEmpty(paths)) {
|
||||
provider.activePaths = paths;
|
||||
|
@@ -14,8 +14,11 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { ScriptDataService } from '../../../core/data/processes/script-data.service';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create Access Control related menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class AccessControlMenuProvider extends AbstractExpandableMenuProvider {
|
||||
|
||||
|
@@ -11,8 +11,11 @@ import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the Admin search menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class AdminSearchMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -17,8 +17,11 @@ import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { TextMenuItemModel } from '../menu-item/models/text.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the browse menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class BrowseMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -14,10 +14,13 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { SubscriptionModalComponent } from '../../subscriptions/subscription-modal/subscription-modal.component';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { OnClickMenuItemModel } from '../menu-item/models/onclick.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the subscribe menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class SubscribeMenuProvider extends DSpaceObjectPageMenuProvider {
|
||||
constructor(
|
||||
|
@@ -9,8 +9,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of, } from 'rxjs';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the community list menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class CommunityListMenuProvider extends AbstractMenuProvider {
|
||||
public getSections(): Observable<PartialMenuSection[]> {
|
||||
|
@@ -12,8 +12,11 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { LinkMenuItemModel } from '../menu-item/models/link.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the curation menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class CurationMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -15,9 +15,12 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { URLCombiner } from '../../../core/url-combiner/url-combiner';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { LinkMenuItemModel } from '../menu-item/models/link.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
|
||||
/**
|
||||
* Menu provider to create the DSO edit menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class DSpaceObjectEditMenuProvider extends DSpaceObjectPageMenuProvider {
|
||||
constructor(
|
||||
|
@@ -9,11 +9,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of, } from 'rxjs';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
/**
|
||||
* Menu provider to create the parent wrapper menu of the various DSO page menu sections
|
||||
* This section will be rendered as a button on the DSO pages if sub providers have been added
|
||||
*/
|
||||
@Injectable()
|
||||
export class DsoOptionMenu extends DSpaceObjectPageMenuProvider {
|
||||
|
||||
|
@@ -22,8 +22,11 @@ import {
|
||||
} from '../../dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the admin sidebar edit menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class EditMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -20,8 +20,11 @@ import {
|
||||
} from '../../dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the export menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class ExportMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -11,8 +11,11 @@ import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the health menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class HealthMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -12,8 +12,14 @@ import { AbstractRouteContextMenuProvider } from './route-context.menu';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { hasValue } from '../../../empty.util';
|
||||
|
||||
/**
|
||||
* Helper provider for DSpace object page based menus
|
||||
*/
|
||||
export abstract class DSpaceObjectPageMenuProvider extends AbstractRouteContextMenuProvider<DSpaceObject> {
|
||||
|
||||
/**
|
||||
* Retrieve the dso from the current route data
|
||||
*/
|
||||
public getRouteContext(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<DSpaceObject | undefined> {
|
||||
const dsoRD: RemoteData<DSpaceObject> = route.data.dso;
|
||||
if (hasValue(dsoRD) && dsoRD.hasSucceeded && hasValue(dsoRD.payload)) {
|
||||
@@ -24,7 +30,7 @@ export abstract class DSpaceObjectPageMenuProvider extends AbstractRouteContextM
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dso or entity type for an object to be used in generic messages
|
||||
* Retrieve the dso or entity type for an object to be used in section messages
|
||||
*/
|
||||
protected getDsoType(dso: DSpaceObject) {
|
||||
const renderType = dso.getRenderTypes()[0];
|
||||
|
@@ -5,29 +5,35 @@
|
||||
*
|
||||
* http://www.dspace.org/license/
|
||||
*/
|
||||
import { combineLatest, Observable, of as observableOf, } from 'rxjs';
|
||||
import { combineLatest, Observable, } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Helper provider for basic expandable menus
|
||||
*/
|
||||
export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvider {
|
||||
|
||||
alwaysRenderExpandable = true;
|
||||
|
||||
|
||||
/**
|
||||
* Get the top section for this expandable menu
|
||||
*/
|
||||
abstract getTopSection(): Observable<PartialMenuSection>;
|
||||
|
||||
/**
|
||||
* Get the subsections for this expandable menu
|
||||
*/
|
||||
abstract getSubSections(): Observable<PartialMenuSection[]>;
|
||||
|
||||
protected includeSubSections(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all sections
|
||||
* This method will combine both the top section and subsections
|
||||
*/
|
||||
getSections(): Observable<PartialMenuSection[]> {
|
||||
const full = this.includeSubSections();
|
||||
|
||||
return combineLatest([
|
||||
this.getTopSection(),
|
||||
full ? this.getSubSections() : observableOf([]),
|
||||
this.getSubSections(),
|
||||
]).pipe(
|
||||
map((
|
||||
[partialTopSection, partialSubSections]: [PartialMenuSection, PartialMenuSection[]]
|
||||
@@ -35,8 +41,9 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide
|
||||
const subSections = partialSubSections.map((partialSub, index) => {
|
||||
return {
|
||||
...partialSub,
|
||||
id: partialSub.id ?? `${this.menuProviderId}_Sub-${index}`,
|
||||
id: partialSub.id ?? `${this.menuProviderId}_${index}`,
|
||||
parentID: this.menuProviderId,
|
||||
alwaysRenderExpandable: false,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -45,6 +52,7 @@ export abstract class AbstractExpandableMenuProvider extends AbstractMenuProvide
|
||||
{
|
||||
...partialTopSection,
|
||||
id: this.menuProviderId,
|
||||
alwaysRenderExpandable: this.alwaysRenderExpandable,
|
||||
},
|
||||
];
|
||||
})
|
||||
|
@@ -8,8 +8,11 @@
|
||||
import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router';
|
||||
import { Observable, of as observableOf, } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Helper provider for route dependent menus
|
||||
*/
|
||||
export abstract class AbstractRouteContextMenuProvider<T> extends AbstractMenuProvider {
|
||||
shouldPersistOnRouteChange = false;
|
||||
|
||||
|
@@ -14,8 +14,11 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService, } from '../../../core/data/processes/script-data.service';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the import menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class ImportMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -19,11 +19,14 @@ import { NotificationsService } from '../../notifications/notifications.service'
|
||||
import { MenuID } from '../menu-id.model';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { OnClickMenuItemModel } from '../menu-item/models/onclick.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { MenuService } from '../menu.service';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the menu section on person entity pages to claim a researcher by creating a profile
|
||||
*/
|
||||
@Injectable()
|
||||
export class ClaimMenuProvider extends DSpaceObjectPageMenuProvider {
|
||||
constructor(
|
||||
|
@@ -15,9 +15,12 @@ import { Item } from '../../../core/shared/item.model';
|
||||
import { URLCombiner } from '../../../core/url-combiner/url-combiner';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { LinkMenuItemModel } from '../menu-item/models/link.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
|
||||
/**
|
||||
* Menu provider to create the Orcid synchronisation menu section on person entity pages
|
||||
*/
|
||||
@Injectable()
|
||||
export class OrcidMenuProvider extends DSpaceObjectPageMenuProvider {
|
||||
constructor(
|
||||
|
@@ -14,9 +14,12 @@ import { Item } from '../../../core/shared/item.model';
|
||||
import { DsoVersioningModalService } from '../../dso-page/dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { OnClickMenuItemModel } from '../menu-item/models/onclick.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu';
|
||||
|
||||
/**
|
||||
* Menu provider to create the versioning menu section on item pages
|
||||
*/
|
||||
@Injectable()
|
||||
export class VersioningMenuProvider extends DSpaceObjectPageMenuProvider {
|
||||
constructor(
|
||||
|
@@ -23,8 +23,11 @@ import {
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { TextMenuItemModel } from '../menu-item/models/text.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the admin sidebar new menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class NewMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -11,8 +11,11 @@ import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the scripts and processes menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class ProcessesMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -14,8 +14,11 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { ScriptDataService } from '../../../core/data/processes/script-data.service';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractExpandableMenuProvider, } from './helper-providers/expandable-menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the registries menu sections
|
||||
*/
|
||||
@Injectable()
|
||||
export class RegistriesMenuProvider extends AbstractExpandableMenuProvider {
|
||||
constructor(
|
||||
|
@@ -11,17 +11,18 @@ import { ActivatedRouteSnapshot, RouterStateSnapshot, } from '@angular/router';
|
||||
import { Observable, of, } from 'rxjs';
|
||||
import { hasNoValue, hasValue } from '../../empty.util';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { PartialMenuSection } from '../menu-provider';
|
||||
import { PartialMenuSection } from '../menu-provider.model';
|
||||
import { AbstractRouteContextMenuProvider } from './helper-providers/route-context.menu';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { getDSORoute } from '../../../app-routing-paths';
|
||||
|
||||
interface StatisticsLink {
|
||||
id: string,
|
||||
link: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu provider to create the statistics menu section depending on the page it is on
|
||||
* When the user is on a DSO page or a derivative, this menu section will contain a link to the statistics of that DSO
|
||||
* In all other cases the menu section will contain a link to the repository wide statistics
|
||||
*/
|
||||
@Injectable()
|
||||
export class StatisticsMenuProvider extends AbstractRouteContextMenuProvider<DSpaceObject> {
|
||||
|
||||
|
@@ -11,8 +11,11 @@ import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the system wide alert menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class SystemWideAlertMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -11,8 +11,11 @@ import { combineLatest, map, Observable, } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { MenuItemType } from '../menu-item-type.model';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider';
|
||||
import { AbstractMenuProvider, PartialMenuSection, } from '../menu-provider.model';
|
||||
|
||||
/**
|
||||
* Menu provider to create the workflow admin menu section
|
||||
*/
|
||||
@Injectable()
|
||||
export class WorkflowMenuProvider extends AbstractMenuProvider {
|
||||
constructor(
|
||||
|
@@ -273,9 +273,6 @@ import {
|
||||
AdvancedClaimedTaskActionRatingComponent
|
||||
} from './mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component';
|
||||
import { ClaimedTaskActionsDeclineTaskComponent } from './mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component';
|
||||
import {
|
||||
DsoPageSubscriptionButtonComponent
|
||||
} from './dso-page/dso-page-subscription-button/dso-page-subscription-button.component';
|
||||
import { EpersonGroupListComponent } from './eperson-group-list/eperson-group-list.component';
|
||||
import { EpersonSearchBoxComponent } from './eperson-group-list/eperson-search-box/eperson-search-box.component';
|
||||
import { GroupSearchBoxComponent } from './eperson-group-list/group-search-box/group-search-box.component';
|
||||
@@ -395,7 +392,6 @@ const COMPONENTS = [
|
||||
ItemPageTitleFieldComponent,
|
||||
ThemedSearchNavbarComponent,
|
||||
ListableNotificationObjectComponent,
|
||||
DsoPageSubscriptionButtonComponent,
|
||||
MetadataFieldWrapperComponent,
|
||||
ContextHelpWrapperComponent,
|
||||
EpersonGroupListComponent,
|
||||
|
@@ -70,7 +70,7 @@ export class ServerInitService extends InitService {
|
||||
this.initAngulartics();
|
||||
this.initRouteListeners();
|
||||
this.themeService.listenForThemeChanges(false);
|
||||
// this.initPersistentMenus();
|
||||
this.menuProviderService.initPersistentMenus();
|
||||
|
||||
await this.authenticationReady$().toPromise();
|
||||
|
||||
|
Reference in New Issue
Block a user