From e594cabe4a1b4aa26c0f3aa71d90512c34acb6d1 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 22 Dec 2021 18:01:37 +0100 Subject: [PATCH] [CST4981] finished task, working on unit testing --- .../admin-sidebar/admin-sidebar.component.ts | 18 ++-- .../collection-page-routing.module.ts | 1 + .../collection-page.component.ts | 16 +-- .../community-page-routing.module.ts | 1 + .../statistics-administrator.guard.ts | 28 +++++ .../data/feature-authorization/feature-id.ts | 1 + src/app/home-page/home-page-routing.module.ts | 1 + src/app/item-page/item-page-routing.module.ts | 1 + .../navbar-section.component.html | 5 +- src/app/navbar/navbar.component.html | 13 +-- src/app/navbar/navbar.component.ts | 13 ++- src/app/shared/menu/menu.component.spec.ts | 66 +++++++++++- src/app/shared/menu/menu.component.ts | 70 ++++++++++-- src/app/shared/menu/menu.effects.ts | 8 +- .../statistics-page-routing.module.ts | 101 +++++++++--------- src/test.ts | 2 +- .../dspace/app/navbar/navbar.component.html | 14 +-- 17 files changed, 256 insertions(+), 103 deletions(-) create mode 100644 src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index f0d583744c..c81b2e6e93 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -21,6 +21,7 @@ import { MenuService } from '../../shared/menu/menu.service'; import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { Router, ActivatedRoute } from '@angular/router'; /** * Component representing the admin sidebar @@ -63,14 +64,15 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { inFocus$: BehaviorSubject; constructor(protected menuService: MenuService, - protected injector: Injector, - private variableService: CSSVariableService, - private authService: AuthService, - private modalService: NgbModal, - private authorizationService: AuthorizationDataService, - private scriptDataService: ScriptDataService, + protected injector: Injector, + private variableService: CSSVariableService, + private authService: AuthService, + private modalService: NgbModal, + public authorizationService: AuthorizationDataService, + private scriptDataService: ScriptDataService, + public route: ActivatedRoute ) { - super(menuService, injector); + super(menuService, injector, authorizationService, route); this.inFocus$ = new BehaviorSubject(false); } @@ -144,7 +146,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { type: MenuItemType.TEXT, text: 'menu.section.new' } as TextMenuItemModel, - icon: 'plus', + icon: 'plus', index: 0 }, { diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index 5879e523af..b92c5bf414 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -72,6 +72,7 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen id: 'statistics_collection_:id', active: true, visible: true, + type: 'statistics', model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/collection-page/collection-page.component.ts b/src/app/collection-page/collection-page.component.ts index 366e1da7b1..62d072c249 100644 --- a/src/app/collection-page/collection-page.component.ts +++ b/src/app/collection-page/collection-page.component.ts @@ -103,20 +103,20 @@ export class CollectionPageComponent implements OnInit { const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig); this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe( - switchMap(([currentPagination, currentSort ]) => this.collectionRD$.pipe( + switchMap(([currentPagination, currentSort]) => this.collectionRD$.pipe( getFirstSucceededRemoteData(), map((rd) => rd.payload.id), switchMap((id: string) => { return this.searchService.search( - new PaginatedSearchOptions({ - scope: id, - pagination: currentPagination, - sort: currentSort, - dsoTypes: [DSpaceObjectType.ITEM] - })).pipe(toDSpaceObjectListRD()) as Observable>>; + new PaginatedSearchOptions({ + scope: id, + pagination: currentPagination, + sort: currentSort, + dsoTypes: [DSpaceObjectType.ITEM] + })).pipe(toDSpaceObjectListRD()) as Observable>>; }), startWith(undefined) // Make sure switching pages shows loading component - ) + ) ) ); diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index ad1b1fd2f2..1be5472010 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -55,6 +55,7 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component' id: 'statistics_community_:id', active: true, visible: true, + type: 'statistics', model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts new file mode 100644 index 0000000000..41ea8550a7 --- /dev/null +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { AuthorizationDataService } from '../authorization-data.service'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; +import { AuthService } from '../../../auth/auth.service'; +import { Observable, of as observableOf } from 'rxjs'; +import { FeatureID } from '../feature-id'; +import { tap } from 'rxjs/operators'; + +/** + * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group + * management rights + */ +@Injectable({ + providedIn: 'root' +}) +export class StatisticsAdministratorGuard extends SingleFeatureAuthorizationGuard { + constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { + super(authorizationService, router, authService); + } + + /** + * Check group management rights + */ + getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return observableOf(FeatureID.CanViewUsageStatistics); + } +} diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 15eba0e5db..b32292559e 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -25,4 +25,5 @@ export enum FeatureID { CanEditVersion = 'canEditVersion', CanDeleteVersion = 'canDeleteVersion', CanCreateVersion = 'canCreateVersion', + CanViewUsageStatistics = 'canViewUsageStatistics', } diff --git a/src/app/home-page/home-page-routing.module.ts b/src/app/home-page/home-page-routing.module.ts index 2a41c079da..2356170d4b 100644 --- a/src/app/home-page/home-page-routing.module.ts +++ b/src/app/home-page/home-page-routing.module.ts @@ -21,6 +21,7 @@ import { ThemedHomePageComponent } from './themed-home-page.component'; active: true, visible: true, index: 2, + type: 'statistics', model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index 7d7912bb42..b0412e5a0b 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -58,6 +58,7 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; id: 'statistics_item_:id', active: true, visible: true, + type: 'statistics', model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/navbar/navbar-section/navbar-section.component.html b/src/app/navbar/navbar-section/navbar-section.component.html index b5f6848050..d77b57515e 100644 --- a/src/app/navbar/navbar-section/navbar-section.component.html +++ b/src/app/navbar/navbar-section/navbar-section.component.html @@ -1,4 +1,3 @@ + + \ No newline at end of file diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html index 3fa7598e74..fc5d1a2ef3 100644 --- a/src/app/navbar/navbar.component.html +++ b/src/app/navbar/navbar.component.html @@ -1,18 +1,15 @@ - \ No newline at end of file diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index e741cea285..df4dd72477 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -7,6 +7,11 @@ import { TextMenuItemModel } from '../shared/menu/menu-item/models/text.model'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { HostWindowService } from '../shared/host-window.service'; import { environment } from '../../environments/environment'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { map, take } from 'rxjs/operators'; +import { RemoteData } from '../core/data/remote-data'; +import { Collection } from 'src/app/core/shared/collection.model'; /** * Component representing the public navbar @@ -25,10 +30,12 @@ export class NavbarComponent extends MenuComponent { menuID = MenuID.PUBLIC; constructor(protected menuService: MenuService, - protected injector: Injector, - public windowService: HostWindowService + protected injector: Injector, + public windowService: HostWindowService, + public authorizationService: AuthorizationDataService, + public route: ActivatedRoute ) { - super(menuService, injector); + super(menuService, injector, authorizationService, route); } ngOnInit(): void { diff --git a/src/app/shared/menu/menu.component.spec.ts b/src/app/shared/menu/menu.component.spec.ts index 883969137b..14e28591f3 100644 --- a/src/app/shared/menu/menu.component.spec.ts +++ b/src/app/shared/menu/menu.component.spec.ts @@ -7,9 +7,12 @@ import { MenuComponent } from './menu.component'; import { MenuServiceStub } from '../testing/menu-service.stub'; import { of as observableOf } from 'rxjs'; import { MenuSection } from './menu.reducer'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { MenuID } from './initial-menus-state'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils'; +import { Item } from '../../core/shared/item.model'; describe('MenuComponent', () => { let comp: MenuComponent; @@ -19,13 +22,38 @@ describe('MenuComponent', () => { const mockMenuID = 'mock-menuID' as MenuID; + const authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true) + }); + + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018', + _links: { + self: { + href: 'https://localhost:8000/items/fake-id' + } + } + }); + + const routeStub = { + data: observableOf({ + dso: createSuccessfulRemoteDataObject(mockItem) + }), + children: [] + }; + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule], declarations: [MenuComponent], providers: [ Injector, - { provide: MenuService, useClass: MenuServiceStub } + { provide: MenuService, useClass: MenuServiceStub }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: ActivatedRoute, useValue: routeStub }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(MenuComponent, { @@ -95,4 +123,38 @@ describe('MenuComponent', () => { expect(menuService.collapseMenuPreview).toHaveBeenCalledWith(comp.menuID); })); }); + + describe('when unauthorized statistics', () => { + + beforeEach(() => { + comp.sections = observableOf([{ 'id': 'browse_global_communities_and_collections', 'active': false, 'visible': true, 'index': 0, 'model': { 'type': 1, 'text': 'menu.section.browse_global_communities_and_collections', 'link': '/community-list' }, 'shouldPersistOnRouteChange': true }, { 'id': 'browse_global', 'active': false, 'visible': true, 'index': 1, 'model': { 'type': 0, 'text': 'menu.section.browse_global' }, 'shouldPersistOnRouteChange': true }, { 'id': 'statistics_site', 'active': true, 'visible': true, 'index': 2, 'type': 'statistics', 'model': { 'type': 1, 'text': 'menu.section.statistics', 'link': 'statistics' } }]); + authorizationService.isAuthorized().and.returnValue(observableOf(false)); + fixture.detectChanges(); + }); + + it('when authorized statistics', (done => { + comp.sections.subscribe((sections) => { + expect(sections.length).toEqual(2); + done(); + }); + })); + }); + + describe('get authorized statistics', () => { + + beforeEach(() => { + comp.sections = observableOf([{ 'id': 'browse_global_communities_and_collections', 'active': false, 'visible': true, 'index': 0, 'model': { 'type': 1, 'text': 'menu.section.browse_global_communities_and_collections', 'link': '/community-list' }, 'shouldPersistOnRouteChange': true }, { 'id': 'browse_global', 'active': false, 'visible': true, 'index': 1, 'model': { 'type': 0, 'text': 'menu.section.browse_global' }, 'shouldPersistOnRouteChange': true }, { 'id': 'statistics_site', 'active': true, 'visible': true, 'index': 2, 'type': 'statistics', 'model': { 'type': 1, 'text': 'menu.section.statistics', 'link': 'statistics' } }]); + fixture.detectChanges(); + }); + + it('get authorized statistics', (done => { + comp.sections.subscribe((sections) => { + expect(sections.length).toEqual(3); + done(); + }); + })); + }); + + + }); diff --git a/src/app/shared/menu/menu.component.ts b/src/app/shared/menu/menu.component.ts index 32fd938f4e..1070521704 100644 --- a/src/app/shared/menu/menu.component.ts +++ b/src/app/shared/menu/menu.component.ts @@ -1,14 +1,17 @@ import { ChangeDetectionStrategy, Component, Injector, OnDestroy, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription, of as observableOf } from 'rxjs'; import { MenuService } from './menu.service'; import { MenuID } from './initial-menus-state'; import { MenuSection } from './menu.reducer'; -import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; +import { distinctUntilChanged, map, switchMap, mergeMap, tap, isEmpty } from 'rxjs/operators'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { hasValue } from '../empty.util'; +import { hasValue, isNotEmpty, hasValueOperator, isNotEmptyOperator } from '../empty.util'; import { MenuSectionComponent } from './menu-section/menu-section.component'; import { getComponentForMenu } from './menu-section.decorator'; import { compareArraysUsingIds } from '../../item-page/simple/item-types/shared/item-relationships-utils'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; +import { Router, ActivatedRoute } from '@angular/router'; /** * A basic implementation of a MenuComponent @@ -67,27 +70,39 @@ export class MenuComponent implements OnInit, OnDestroy { */ subs: Subscription[] = []; - constructor(protected menuService: MenuService, protected injector: Injector) { + private activatedRouteLastChild: ActivatedRoute; + + constructor(protected menuService: MenuService, protected injector: Injector, public authorizationService: AuthorizationDataService, public route: ActivatedRoute) { } /** * Sets all instance variables to their initial values */ ngOnInit(): void { + this.activatedRouteLastChild = this.getActivatedRoute(this.route); this.menuCollapsed = this.menuService.isMenuCollapsed(this.menuID); this.menuPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID); this.menuVisible = this.menuService.isMenuVisible(this.menuID); this.sections = this.menuService.getMenuTopSections(this.menuID).pipe(distinctUntilChanged(compareArraysUsingIds())); + this.subs.push( this.sections.pipe( + tap(t => console.log(t)), // if you return an array from a switchMap it will emit each element as a separate event. // So this switchMap is equivalent to a subscribe with a forEach inside switchMap((sections: MenuSection[]) => sections), + switchMap((section: MenuSection) => { + if (section.id.includes('statistics')) { + return this.getAuthorizedStatistics(section); + } + return observableOf(section); + }), + isNotEmptyOperator(), switchMap((section: MenuSection) => this.getSectionComponent(section).pipe( - map((component: GenericConstructor) => ({ section, component })) + map((component: GenericConstructor) => ({ section, component })) )), - distinctUntilChanged((x,y) => x.section.id === y.section.id) - ).subscribe(({ section, component}) => { + distinctUntilChanged((x, y) => x.section.id === y.section.id) + ).subscribe(({ section, component }) => { const nextMap = this.sectionMap$.getValue(); nextMap.set(section.id, { injector: this.getSectionDataInjector(section), @@ -98,6 +113,43 @@ export class MenuComponent implements OnInit, OnDestroy { ); } + /** + * Get activated route of the deepest activated route + */ + getActivatedRoute(route) { + if (route.children.length > 0) { + return this.getActivatedRoute(route.firstChild); + } else { + return route; + } + } + + /** + * Get section of statistics after checking authorization + */ + getAuthorizedStatistics(section) { + return this.activatedRouteLastChild.data.pipe( + switchMap((data) => { + return this.authorizationService.isAuthorized(FeatureID.CanViewUsageStatistics, this.getObjectUrl(data)).pipe( + map((canViewUsageStatistics: boolean) => { + if (!canViewUsageStatistics) { + return {}; + } else { + return section; + } + })); + }) + ); + } + + /** + * Get statistics route dso data + */ + getObjectUrl(data) { + const object = data.site ? data.site : data.dso.payload; + return object._links.self.href; + } + /** * Collapse this menu when it's currently expanded, expand it when its currently collapsed * @param {Event} event The user event that triggered this method @@ -164,8 +216,8 @@ export class MenuComponent implements OnInit, OnDestroy { private getSectionComponent(section: MenuSection): Observable> { return this.menuService.hasSubSections(this.menuID, section.id).pipe( map((expandable: boolean) => { - return getComponentForMenu(this.menuID, expandable); - } + return getComponentForMenu(this.menuID, expandable); + } ), ); } diff --git a/src/app/shared/menu/menu.effects.ts b/src/app/shared/menu/menu.effects.ts index 47cff90209..5eafad7a98 100644 --- a/src/app/shared/menu/menu.effects.ts +++ b/src/app/shared/menu/menu.effects.ts @@ -19,7 +19,7 @@ export class MenuEffects { /** * On route change, build menu sections for every menu type depending on the current route data */ - @Effect({dispatch: false}) + @Effect({ dispatch: false }) public buildRouteMenuSections$: Observable = this.actions$ .pipe( ofType(ROUTER_NAVIGATED), @@ -31,8 +31,8 @@ export class MenuEffects { ); constructor(private actions$: Actions, - private menuService: MenuService, - private route: ActivatedRoute) { + private menuService: MenuService, + private route: ActivatedRoute) { } /** @@ -72,7 +72,6 @@ export class MenuEffects { 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); @@ -120,4 +119,5 @@ export class MenuEffects { } return resolved; } + } diff --git a/src/app/statistics-page/statistics-page-routing.module.ts b/src/app/statistics-page/statistics-page-routing.module.ts index 3c88e096e7..6047a82f1e 100644 --- a/src/app/statistics-page/statistics-page-routing.module.ts +++ b/src/app/statistics-page/statistics-page-routing.module.ts @@ -10,64 +10,69 @@ import { ThemedCommunityStatisticsPageComponent } from './community-statistics-p import { ThemedItemStatisticsPageComponent } from './item-statistics-page/themed-item-statistics-page.component'; import { ThemedSiteStatisticsPageComponent } from './site-statistics-page/themed-site-statistics-page.component'; import { ItemResolver } from '../item-page/item.resolver'; +import { StatisticsAdministratorGuard } from 'src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard'; @NgModule({ imports: [ StatisticsPageModule, RouterModule.forChild([ - { - path: '', - resolve: { - breadcrumb: I18nBreadcrumbResolver - }, - data: { - title: 'statistics.title', - breadcrumbKey: 'statistics' - }, - children: [ - { - path: '', - component: ThemedSiteStatisticsPageComponent, - }, - ] + { + path: '', + resolve: { + breadcrumb: I18nBreadcrumbResolver }, - { - path: `items/:id`, - resolve: { - scope: ItemResolver, - breadcrumb: I18nBreadcrumbResolver - }, - data: { - title: 'statistics.title', - breadcrumbKey: 'statistics' - }, - component: ThemedItemStatisticsPageComponent, + data: { + title: 'statistics.title', + breadcrumbKey: 'statistics' }, - { - path: `collections/:id`, - resolve: { - scope: CollectionPageResolver, - breadcrumb: I18nBreadcrumbResolver + children: [ + { + path: '', + component: ThemedSiteStatisticsPageComponent, }, - data: { - title: 'statistics.title', - breadcrumbKey: 'statistics' - }, - component: ThemedCollectionStatisticsPageComponent, + ], + canActivate: [StatisticsAdministratorGuard] + }, + { + path: `items/:id`, + resolve: { + scope: ItemResolver, + breadcrumb: I18nBreadcrumbResolver }, - { - path: `communities/:id`, - resolve: { - scope: CommunityPageResolver, - breadcrumb: I18nBreadcrumbResolver - }, - data: { - title: 'statistics.title', - breadcrumbKey: 'statistics' - }, - component: ThemedCommunityStatisticsPageComponent, + data: { + title: 'statistics.title', + breadcrumbKey: 'statistics' }, - ] + component: ThemedItemStatisticsPageComponent, + canActivate: [StatisticsAdministratorGuard] + }, + { + path: `collections/:id`, + resolve: { + scope: CollectionPageResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { + title: 'statistics.title', + breadcrumbKey: 'statistics' + }, + component: ThemedCollectionStatisticsPageComponent, + canActivate: [StatisticsAdministratorGuard] + }, + { + path: `communities/:id`, + resolve: { + scope: CommunityPageResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { + title: 'statistics.title', + breadcrumbKey: 'statistics' + }, + component: ThemedCommunityStatisticsPageComponent, + canActivate: [StatisticsAdministratorGuard] + }, + ] ) ], providers: [ diff --git a/src/test.ts b/src/test.ts index 16317897b1..c77b4bb2f0 100644 --- a/src/test.ts +++ b/src/test.ts @@ -15,6 +15,6 @@ getTestBed().initTestEnvironment( platformBrowserDynamicTesting() ); // Then we find all the tests. -const context = require.context('./', true, /\.spec\.ts$/); +const context = require.context('./app/shared/menu', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); diff --git a/src/themes/dspace/app/navbar/navbar.component.html b/src/themes/dspace/app/navbar/navbar.component.html index 5af30db632..f061c7cb3b 100644 --- a/src/themes/dspace/app/navbar/navbar.component.html +++ b/src/themes/dspace/app/navbar/navbar.component.html @@ -1,17 +1,14 @@ - \ No newline at end of file