From 0fec5395ff56659568d4e3927775d3aa499a542e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 14 Feb 2025 13:11:15 +0100 Subject: [PATCH] Fix merge issues and tests - Migrate create-report sections to a new provider - Fix menu component test - Add dso option sections to com/col sub paths - Fix issue with breadcrumbs on the collection page --- src/app/app-routes.ts | 2 +- src/app/app.menus.ts | 8 ++ .../collection-page/collection-page-routes.ts | 5 +- .../community-page/community-page-routes.ts | 10 +- src/app/shared/menu/menu-route.model.ts | 3 + src/app/shared/menu/menu.component.spec.ts | 3 + .../menu/providers/create-report.menu.spec.ts | 100 ++++++++++++++++++ .../menu/providers/create-report.menu.ts | 93 ++++++++++++++++ .../helper-providers/dso.menu.spec.ts | 16 +-- .../providers/helper-providers/dso.menu.ts | 5 +- .../shared/menu/providers/statistics.menu.ts | 12 ++- 11 files changed, 240 insertions(+), 17 deletions(-) create mode 100644 src/app/shared/menu/providers/create-report.menu.spec.ts create mode 100644 src/app/shared/menu/providers/create-report.menu.ts diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts index db71c866f0..25324a66be 100644 --- a/src/app/app-routes.ts +++ b/src/app/app-routes.ts @@ -106,7 +106,7 @@ export const APP_ROUTES: Route[] = [ path: COLLECTION_MODULE_PATH, loadChildren: () => import('./collection-page/collection-page-routes') .then((m) => m.ROUTES), - data: { showBreadcrumbs: false, enableRSS: true }, + data: { enableRSS: true }, canActivate: [endUserAgreementCurrentUserGuard], }, { diff --git a/src/app/app.menus.ts b/src/app/app.menus.ts index b5bf4f167f..46dba503a9 100644 --- a/src/app/app.menus.ts +++ b/src/app/app.menus.ts @@ -13,6 +13,7 @@ import { AdminSearchMenuProvider } from './shared/menu/providers/admin-search.me import { BrowseMenuProvider } from './shared/menu/providers/browse.menu'; import { SubscribeMenuProvider } from './shared/menu/providers/comcol-subscribe.menu'; import { CommunityListMenuProvider } from './shared/menu/providers/community-list.menu'; +import { CreateReportMenuProvider } from './shared/menu/providers/create-report.menu'; import { CurationMenuProvider } from './shared/menu/providers/curation.menu'; import { DSpaceObjectEditMenuProvider } from './shared/menu/providers/dso-edit.menu'; import { DsoOptionMenuProvider } from './shared/menu/providers/dso-option.menu'; @@ -42,6 +43,7 @@ export const MENUS = buildMenuStructure({ ImportMenuProvider, ExportMenuProvider, AccessControlMenuProvider, + CreateReportMenuProvider, AdminSearchMenuProvider, RegistriesMenuProvider, CurationMenuProvider, @@ -54,11 +56,17 @@ export const MENUS = buildMenuStructure({ DsoOptionMenuProvider.withSubs([ SubscribeMenuProvider.onRoute( MenuRoute.SIMPLE_COMMUNITY_PAGE, + MenuRoute.SIMPLE_COMMUNITY_BROWSE_PAGE, + MenuRoute.SIMPLE_COMMUNITY_SUBCOLCOM_PAGE, MenuRoute.SIMPLE_COLLECTION_PAGE, + MenuRoute.SIMPLE_COLLECTION_BROWSE_PAGE, ), DSpaceObjectEditMenuProvider.onRoute( MenuRoute.SIMPLE_COMMUNITY_PAGE, + MenuRoute.SIMPLE_COMMUNITY_BROWSE_PAGE, + MenuRoute.SIMPLE_COMMUNITY_SUBCOLCOM_PAGE, MenuRoute.SIMPLE_COLLECTION_PAGE, + MenuRoute.SIMPLE_COLLECTION_BROWSE_PAGE, MenuRoute.SIMPLE_ITEM_PAGE, MenuRoute.FULL_ITEM_PAGE, ), diff --git a/src/app/collection-page/collection-page-routes.ts b/src/app/collection-page/collection-page-routes.ts index 9df71148e8..6e368e5953 100644 --- a/src/app/collection-page/collection-page-routes.ts +++ b/src/app/collection-page/collection-page-routes.ts @@ -97,7 +97,10 @@ export const ROUTES: Route[] = [ resolve: { breadcrumb: browseByI18nBreadcrumbResolver, }, - data: { breadcrumbKey: 'browse.metadata' }, + data: { + breadcrumbKey: 'browse.metadata', + menuRoute: MenuRoute.SIMPLE_COLLECTION_BROWSE_PAGE, + }, }, ], }, diff --git a/src/app/community-page/community-page-routes.ts b/src/app/community-page/community-page-routes.ts index ede79d687d..4295ee62a1 100644 --- a/src/app/community-page/community-page-routes.ts +++ b/src/app/community-page/community-page-routes.ts @@ -83,7 +83,10 @@ export const ROUTES: Route[] = [ resolve: { breadcrumb: i18nBreadcrumbResolver, }, - data: { breadcrumbKey: 'community.subcoms-cols' }, + data: { + breadcrumbKey: 'community.subcoms-cols', + menuRoute: MenuRoute.SIMPLE_COMMUNITY_SUBCOLCOM_PAGE, + }, }, { path: 'browse/:id', @@ -93,7 +96,10 @@ export const ROUTES: Route[] = [ resolve: { breadcrumb: browseByI18nBreadcrumbResolver, }, - data: { breadcrumbKey: 'browse.metadata' }, + data: { + breadcrumbKey: 'browse.metadata', + menuRoute: MenuRoute.SIMPLE_COMMUNITY_BROWSE_PAGE, + }, }, ], }, diff --git a/src/app/shared/menu/menu-route.model.ts b/src/app/shared/menu/menu-route.model.ts index db0fe03169..ac65c03805 100644 --- a/src/app/shared/menu/menu-route.model.ts +++ b/src/app/shared/menu/menu-route.model.ts @@ -3,7 +3,10 @@ */ export enum MenuRoute { SIMPLE_COMMUNITY_PAGE = 'simple-community-page', + SIMPLE_COMMUNITY_BROWSE_PAGE = 'simple-community-browse-page', + SIMPLE_COMMUNITY_SUBCOLCOM_PAGE = 'simple-community-subcolcom-page', SIMPLE_COLLECTION_PAGE = 'simple-collection-page', + SIMPLE_COLLECTION_BROWSE_PAGE = 'simple-collection-browse-page', SIMPLE_ITEM_PAGE = 'simple-item-page', FULL_ITEM_PAGE = 'full-item-page', } diff --git a/src/app/shared/menu/menu.component.spec.ts b/src/app/shared/menu/menu.component.spec.ts index 57ad797b37..59aaccb6f8 100644 --- a/src/app/shared/menu/menu.component.spec.ts +++ b/src/app/shared/menu/menu.component.spec.ts @@ -125,6 +125,7 @@ describe('MenuComponent', () => { id: 'section1', active: false, visible: true, + alwaysRenderExpandable: false, model: { type: MenuItemType.LINK, text: 'test', @@ -190,6 +191,7 @@ describe('MenuComponent', () => { id: 'section1', active: false, visible: true, + alwaysRenderExpandable: false, model: { type: MenuItemType.LINK, text: 'test', @@ -201,6 +203,7 @@ describe('MenuComponent', () => { parentID: 'section1', active: false, visible: true, + alwaysRenderExpandable: false, model: { type: MenuItemType.LINK, text: 'test', diff --git a/src/app/shared/menu/providers/create-report.menu.spec.ts b/src/app/shared/menu/providers/create-report.menu.spec.ts new file mode 100644 index 0000000000..0e2a8841d9 --- /dev/null +++ b/src/app/shared/menu/providers/create-report.menu.spec.ts @@ -0,0 +1,100 @@ +/** + * 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/ + */ + +import { TestBed } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs'; + +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { AuthorizationDataServiceStub } from '../../testing/authorization-service.stub'; +import { ConfigurationDataServiceStub } from '../../testing/configuration-data.service.stub'; +import { LinkMenuItemModel } from '../menu-item/models/link.model'; +import { TextMenuItemModel } from '../menu-item/models/text.model'; +import { MenuItemType } from '../menu-item-type.model'; +import { PartialMenuSection } from '../menu-provider.model'; +import { CreateReportMenuProvider } from './create-report.menu'; + +describe('CreateReportMenuProvider', () => { + const expectedTopSection: PartialMenuSection = { + visible: true, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.reports', + } as TextMenuItemModel, + icon: 'file-alt', + }; + + const expectedSubSections: PartialMenuSection[] = [ + { + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.reports.collections', + link: '/admin/reports/collections', + } as LinkMenuItemModel, + icon: 'user-check', + }, + { + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.reports.queries', + link: '/admin/reports/queries', + } as LinkMenuItemModel, + icon: 'user-check', + }, + ]; + + let provider: CreateReportMenuProvider; + let authorizationServiceStub = new AuthorizationDataServiceStub(); + let configurationDataService = new ConfigurationDataServiceStub(); + + beforeEach(() => { + spyOn(authorizationServiceStub, 'isAuthorized').and.callFake((id: FeatureID) => { + if (id === FeatureID.CanManageGroups) { + return observableOf(false); + } else { + return observableOf(true); + } + }); + + spyOn(configurationDataService, 'findByPropertyName').and.callFake((property: string) => { + return createSuccessfulRemoteDataObject$(Object.assign({}, new ConfigurationProperty(), { values: ['true'] })); + }); + + TestBed.configureTestingModule({ + providers: [ + CreateReportMenuProvider, + { provide: AuthorizationDataService, useValue: authorizationServiceStub }, + { provide: ConfigurationDataService, useValue: configurationDataService }, + ], + }); + provider = TestBed.inject(CreateReportMenuProvider); + }); + + it('should be created', () => { + expect(provider).toBeTruthy(); + }); + + it('getTopSection should return expected menu section', (done) => { + provider.getTopSection().subscribe((section) => { + expect(section).toEqual(expectedTopSection); + done(); + }); + }); + + it('getSubSections should return expected menu sections', (done) => { + provider.getSubSections().subscribe((sections) => { + expect(sections).toEqual(expectedSubSections); + done(); + }); + }); +}); diff --git a/src/app/shared/menu/providers/create-report.menu.ts b/src/app/shared/menu/providers/create-report.menu.ts new file mode 100644 index 0000000000..0b326d9355 --- /dev/null +++ b/src/app/shared/menu/providers/create-report.menu.ts @@ -0,0 +1,93 @@ +/** + * 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/ + */ + +import { Injectable } from '@angular/core'; +import { + combineLatest as observableCombineLatest, + Observable, +} from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { LinkMenuItemModel } from '../menu-item/models/link.model'; +import { TextMenuItemModel } from '../menu-item/models/text.model'; +import { MenuItemType } from '../menu-item-type.model'; +import { PartialMenuSection } from '../menu-provider.model'; +import { AbstractExpandableMenuProvider } from './helper-providers/expandable-menu-provider'; + +/** + * Menu provider to create the report menu sections + */ +@Injectable() +export class CreateReportMenuProvider extends AbstractExpandableMenuProvider { + constructor( + protected authorizationService: AuthorizationDataService, + protected configurationDataService: ConfigurationDataService, + ) { + super(); + } + + getSubSections(): Observable { + return observableCombineLatest([ + this.configurationDataService.findByPropertyName('contentreport.enable').pipe( + getFirstCompletedRemoteData(), + map((res: RemoteData) => res.hasSucceeded && res.payload && res.payload.values[0] === 'true'), + ), + this.authorizationService.isAuthorized(FeatureID.AdministratorOf), + ]).pipe( + map(([reportEnabled, isSiteAdmin]: [boolean, boolean]) => { + return [ + /* Collections Report */ + { + visible: isSiteAdmin && reportEnabled, + model: { + type: MenuItemType.LINK, + text: 'menu.section.reports.collections', + link: '/admin/reports/collections', + } as LinkMenuItemModel, + icon: 'user-check', + }, + /* Queries Report */ + { + visible: isSiteAdmin && reportEnabled, + model: { + type: MenuItemType.LINK, + text: 'menu.section.reports.queries', + link: '/admin/reports/queries', + } as LinkMenuItemModel, + icon: 'user-check', + }, + ]; + })); + } + + getTopSection(): Observable { + return observableCombineLatest([ + this.configurationDataService.findByPropertyName('contentreport.enable').pipe( + getFirstCompletedRemoteData(), + map((res: RemoteData) => res.hasSucceeded && res.payload && res.payload.values[0] === 'true'), + ), + this.authorizationService.isAuthorized(FeatureID.AdministratorOf), + ]).pipe( + map(([reportEnabled, isSiteAdmin]: [boolean, boolean]) => { + return { + visible: isSiteAdmin && reportEnabled, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.reports', + } as TextMenuItemModel, + icon: 'file-alt', + }; + })); + } +} diff --git a/src/app/shared/menu/providers/helper-providers/dso.menu.spec.ts b/src/app/shared/menu/providers/helper-providers/dso.menu.spec.ts index 38f59a430c..0270d38715 100644 --- a/src/app/shared/menu/providers/helper-providers/dso.menu.spec.ts +++ b/src/app/shared/menu/providers/helper-providers/dso.menu.spec.ts @@ -14,7 +14,7 @@ describe('DSpaceObjectPageMenuProvider', () => { const item: Item = Object.assign(new Item(), { uuid: 'test-item-uuid', type: ITEM.value, - _links: {self: {href: 'self-link'}}, + _links: { self: { href: 'self-link' } }, metadata: { 'dc.title': [{ 'value': 'Untyped Item', @@ -25,7 +25,7 @@ describe('DSpaceObjectPageMenuProvider', () => { const item2: Item = Object.assign(new Item(), { uuid: 'test-item2-uuid', type: ITEM.value, - _links: {self: {href: 'self-link'}}, + _links: { self: { href: 'self-link' } }, metadata: { 'dc.title': [{ 'value': 'Untyped Item 2', @@ -36,7 +36,7 @@ describe('DSpaceObjectPageMenuProvider', () => { const person: Item = Object.assign(new Item(), { uuid: 'test-uuid', type: ITEM.value, - _links: {self: {href: 'self-link'}}, + _links: { self: { href: 'self-link' } }, metadata: { 'dc.title': [{ 'value': 'Person Entity', @@ -50,7 +50,7 @@ describe('DSpaceObjectPageMenuProvider', () => { const collection: Collection = Object.assign(new Collection(), { uuid: 'test-collection-uuid', type: COLLECTION.value, - _links: {self: {href: 'self-link'}}, + _links: { self: { href: 'self-link' } }, metadata: { 'dc.title': [{ 'value': 'Collection', @@ -75,7 +75,7 @@ describe('DSpaceObjectPageMenuProvider', () => { describe('getRouteContext', () => { it('should get the dso from the route', (done) => { - const route = {data: {dso: createSuccessfulRemoteDataObject(item)}} as any; + const route = { data: { dso: createSuccessfulRemoteDataObject(item) } } as any; provider.getRouteContext(route, undefined).subscribe((dso) => { expect(dso).toEqual(item); @@ -89,8 +89,8 @@ describe('DSpaceObjectPageMenuProvider', () => { parent: { data: {}, parent: { - data: {dso: createSuccessfulRemoteDataObject(item)}, - parent: {data: {dso: createSuccessfulRemoteDataObject(item2)}}, + data: { dso: createSuccessfulRemoteDataObject(item) }, + parent: { data: { dso: createSuccessfulRemoteDataObject(item2) } }, }, }, } as any; @@ -101,7 +101,7 @@ describe('DSpaceObjectPageMenuProvider', () => { }); }); it('should return undefined when no dso is found in the route', (done) => { - const route = {data: {}, parent: {data: {}, parent: {data: {}, parent: {data: {}}}}} as any; + const route = { data: {}, parent: { data: {}, parent: { data: {}, parent: { data: {} } } } } as any; provider.getRouteContext(route, undefined).subscribe((dso) => { expect(dso).toBeUndefined(); diff --git a/src/app/shared/menu/providers/helper-providers/dso.menu.ts b/src/app/shared/menu/providers/helper-providers/dso.menu.ts index f19b1cbe53..6440a8340e 100644 --- a/src/app/shared/menu/providers/helper-providers/dso.menu.ts +++ b/src/app/shared/menu/providers/helper-providers/dso.menu.ts @@ -16,7 +16,10 @@ import { import { RemoteData } from '../../../../core/data/remote-data'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { hasNoValue, hasValue } from '../../../empty.util'; +import { + hasNoValue, + hasValue, +} from '../../../empty.util'; import { AbstractRouteContextMenuProvider } from './route-context.menu'; /** diff --git a/src/app/shared/menu/providers/statistics.menu.ts b/src/app/shared/menu/providers/statistics.menu.ts index bf7cb2c3a1..b7bbd96e29 100644 --- a/src/app/shared/menu/providers/statistics.menu.ts +++ b/src/app/shared/menu/providers/statistics.menu.ts @@ -7,15 +7,19 @@ */ import { Injectable } from '@angular/core'; -import { combineLatest, map, Observable, } from 'rxjs'; +import { + combineLatest, + map, + Observable, +} from 'rxjs'; +import { getDSORoute } from '../../../app-routing-paths'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; -import { hasValue, } from '../../empty.util'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { hasValue } from '../../empty.util'; import { MenuItemType } from '../menu-item-type.model'; import { PartialMenuSection } from '../menu-provider.model'; -import { DSpaceObject } from '../../../core/shared/dspace-object.model'; -import { getDSORoute } from '../../../app-routing-paths'; import { DSpaceObjectPageMenuProvider } from './helper-providers/dso.menu'; /**