From bc00165edb707293d91489f1c5dee5fb5afb045d Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 24 Jan 2023 16:16:28 +0200 Subject: [PATCH 01/33] [DSC-909] Make Communities and Collections menu available in the Admin menu bar --- src/app/menu.resolver.ts | 25 +++++++++++++++++++++++++ src/config/default-app-config.ts | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 4eaca1ab6a6..86b7a1445fb 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -271,6 +271,31 @@ export class MenuResolver implements Resolve { this.authorizationService.isAuthorized(FeatureID.CanEditItem), ]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem]) => { const newSubMenuList = [ + /* Communities and Collections */ + { + id: `browse_global_communities_and_collections`, + active: false, + visible: !environment.layout.navbar.showCommunityCollection && isCollectionAdmin, + model: { + type: MenuItemType.LINK, + text: `menu.section.communities_and_collections`, + link: `/community-list` + } as LinkMenuItemModel, + icon: 'users', + index: 0 + }, + /* News */ + { + id: 'new', + active: false, + visible: isCollectionAdmin || isCommunityAdmin || isSiteAdmin, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.new' + } as TextMenuItemModel, + icon: 'plus', + index: 0 + }, { id: 'new_community', parentID: 'new', diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 55b4cdd2f01..45f4b702c6a 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -596,7 +596,7 @@ export class DefaultAppConfig implements AppConfig { layout: LayoutConfig = { navbar: { // If true, show the "Community and Collections" link in the navbar; otherwise, show it in the admin sidebar - showCommunityCollection: true, + showCommunityCollection: false, } }; From 1b45ef5fdd8e2a9c6349c6361eb6fe12603efb20 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Wed, 17 Jan 2024 08:59:03 +0200 Subject: [PATCH 02/33] [DSC-909] remove section added by accident during merge conflict --- src/app/menu.resolver.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 86b7a1445fb..bcfccb502fb 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -284,18 +284,6 @@ export class MenuResolver implements Resolve { icon: 'users', index: 0 }, - /* News */ - { - id: 'new', - active: false, - visible: isCollectionAdmin || isCommunityAdmin || isSiteAdmin, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.new' - } as TextMenuItemModel, - icon: 'plus', - index: 0 - }, { id: 'new_community', parentID: 'new', From d7c331a06499605c322ee56f71ec9b4764d1d91f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 12 Mar 2024 19:34:11 +0100 Subject: [PATCH 03/33] [DSC-1454] limit author to first 20 entries to avoid issue with item page rendering --- src/app/core/metadata/metadata.service.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 9e27ce5cf00..4bd45a5da3f 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -8,12 +8,12 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest, - Observable, - of as observableOf, concat as observableConcat, - EMPTY + EMPTY, + Observable, + of as observableOf } from 'rxjs'; -import { filter, map, switchMap, take, mergeMap } from 'rxjs/operators'; +import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators'; import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util'; import { DSONameService } from '../breadcrumbs/dso-name.service'; @@ -25,10 +25,7 @@ import { BitstreamFormat } from '../shared/bitstream-format.model'; import { Bitstream } from '../shared/bitstream.model'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; -import { - getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload -} from '../shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators'; import { RootDataService } from '../data/root-data.service'; import { getBitstreamDownloadRoute } from '../../app-routing-paths'; import { BundleDataService } from '../data/bundle-data.service'; @@ -245,7 +242,8 @@ export class MetadataService { * Add to the */ private setCitationAuthorTags(): void { - const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']); + // limit author to first 20 entries to avoid issue with item page rendering + const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']).slice(0, 20); this.addMetaTags('citation_author', values); } From 09e5dd069f88fa48a84d3a1630e490c2648bd0b0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 12 Mar 2024 23:21:55 +0100 Subject: [PATCH 04/33] [DSC-1454] limit metadata value list in the full item page --- .../full/full-item-page.component.html | 9 +- .../full/full-item-page.component.spec.ts | 97 ++++++++++++++++++- .../full/full-item-page.component.ts | 22 ++++- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index 96767a90879..651fe4ff1c4 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -20,11 +20,18 @@ - + + + +
{{mdEntry.key}} {{mdValue.value}} {{mdValue.language}}
+ +
diff --git a/src/app/item-page/full/full-item-page.component.spec.ts b/src/app/item-page/full/full-item-page.component.spec.ts index 9fc078c2cd7..f077d8497ab 100644 --- a/src/app/item-page/full/full-item-page.component.spec.ts +++ b/src/app/item-page/full/full-item-page.component.spec.ts @@ -32,6 +32,77 @@ const mockItem: Item = Object.assign(new Item(), { language: 'en_US', value: 'test item' } + ], + 'dc.contributor.author': [ + { + value: 'author1' + }, + { + value: 'author2' + }, + { + value: 'author3' + }, + { + value: 'author4' + }, + { + value: 'author5' + }, + { + value: 'author6' + }, + { + value: 'author7' + }, + { + value: 'author8' + }, + { + value: 'author9' + }, + { + value: 'author10' + }, + { + value: 'author11' + }, + { + value: 'author12' + }, + { + value: 'author13' + }, + { + value: 'author14' + }, + { + value: 'author15' + }, + { + value: 'author16' + }, + { + value: 'author17' + }, + { + value: 'author18' + }, + { + value: 'author19' + }, + { + value: 'author20' + }, + { + value: 'author21' + }, + { + value: 'author22' + }, + { + value: 'author23' + } ] } }); @@ -142,7 +213,7 @@ describe('FullItemPageComponent', () => { it('should display the item\'s metadata', () => { const table = fixture.debugElement.query(By.css('table')); - for (const metadatum of mockItem.allMetadata(Object.keys(mockItem.metadata))) { + for (const metadatum of mockItem.allMetadata('dc.title')) { expect(table.nativeElement.innerHTML).toContain(metadatum.value); } }); @@ -227,4 +298,28 @@ describe('FullItemPageComponent', () => { expect(linkHeadService.addTag).toHaveBeenCalledTimes(2); }); }); + + describe('when the item has many metadata values', () => { + beforeEach(() => { + comp.itemRD$ = new BehaviorSubject>(createSuccessfulRemoteDataObject(mockItem)); + fixture.detectChanges(); + }); + + it('should not display all the item\'s metadata', () => { + const table = fixture.debugElement.query(By.css('table')); + const visibleValues = mockItem.allMetadata('dc.contributor.author').slice(0, 20); + const hiddenValues = mockItem.allMetadata('dc.contributor.author').slice(20, 40); + for (const metadatum of visibleValues) { + expect(table.nativeElement.innerHTML).toContain(metadatum.value); + } + for (const metadatum of hiddenValues) { + expect(table.nativeElement.innerHTML).not.toContain(metadatum.value); + } + }); + + it('should display show more button', () => { + const btn = fixture.debugElement.query(By.css('button[data-test="btn-more"]')); + expect(btn).not.toBeNull(); + }); + }); }); diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 31dd2c5fc28..6ab2b06eca8 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -1,4 +1,4 @@ -import { filter, map } from 'rxjs/operators'; +import { filter, map, tap } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { ActivatedRoute, Data, Router } from '@angular/router'; @@ -38,6 +38,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, metadata$: Observable; + metadataMapLimit$: BehaviorSubject> = new BehaviorSubject>(new Map()); + + limitSize = 20; + /** * True when the itemRD has been originated from its workspaceite/workflowitem, false otherwise. */ @@ -66,7 +70,15 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, this.metadata$ = this.itemRD$.pipe( map((rd: RemoteData) => rd.payload), filter((item: Item) => hasValue(item)), - map((item: Item) => item.metadata),); + map((item: Item) => item.metadata), + tap((metadataMap: MetadataMap) => { + const metadataMapLimit: Map = new Map(); + Object.keys(metadataMap).forEach((key: string) => { + metadataMapLimit.set(key, this.limitSize); + }); + this.metadataMapLimit$.next(metadataMapLimit); + }) + ); this.subs.push(this.route.data.subscribe((data: Data) => { this.fromSubmissionObject = hasValue(data.wfi) || hasValue(data.wsi); @@ -84,4 +96,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, ngOnDestroy() { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } + + increaseLimit(key: string) { + const tmpMap: Map = this.metadataMapLimit$.value; + tmpMap.set(key, tmpMap.get(key) + this.limitSize); + this.metadataMapLimit$.next(tmpMap); + } } From cd42fde08845d33dec63a1412fadbf3929b8db5c Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Thu, 21 Dec 2023 13:44:30 +0100 Subject: [PATCH 05/33] [DSC-1437] Extend lucky search for supporting bitstream --- .../search/lucky-search.component.html | 15 ++ .../search/lucky-search.component.spec.ts | 192 +++++++++++++----- .../search/lucky-search.component.ts | 120 ++++++++--- src/app/shared/mocks/router.mock.ts | 4 + 4 files changed, 254 insertions(+), 77 deletions(-) diff --git a/src/app/lucky-search/search/lucky-search.component.html b/src/app/lucky-search/search/lucky-search.component.html index 602666c2087..d54c800df6f 100644 --- a/src/app/lucky-search/search/lucky-search.component.html +++ b/src/app/lucky-search/search/lucky-search.component.html @@ -29,3 +29,18 @@
{{'lucky.search.results.notfound' | translate}} {{curren + +
+
+ + + + {{fileName(attachment)}} ({{getSize(attachment) | dsFileSize}}) + + + + {{getDescription(attachment)}} + + +
+
diff --git a/src/app/lucky-search/search/lucky-search.component.spec.ts b/src/app/lucky-search/search/lucky-search.component.spec.ts index 5ba5d165a22..57de7472870 100644 --- a/src/app/lucky-search/search/lucky-search.component.spec.ts +++ b/src/app/lucky-search/search/lucky-search.component.spec.ts @@ -15,6 +15,12 @@ import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { MetadataMap, MetadataValue } from '../../core/shared/metadata.models'; describe('SearchComponent', () => { let fixture: ComponentFixture; @@ -39,6 +45,9 @@ describe('SearchComponent', () => { } ])) }); + const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { + findByItem: jasmine.createSpy('findByItem') + }); const mockSearchOptions = observableOf(new PaginatedSearchOptions({ pagination: Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', @@ -52,18 +61,12 @@ describe('SearchComponent', () => { }; let component: LuckySearchComponent; - const itemPageUrl = '/lucky-search?index=xxx&value=yyyy'; const urlTree = new UrlTree(); urlTree.queryParams = { index: 'test', 'value': 'test' }; - const routerStub = jasmine.createSpyObj('router', { - parseUrl: urlTree, - createUrlTree: new UrlTree(), - url: itemPageUrl, - navigateByUrl: void {} - }); + const routerStub = new RouterMock(); beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [LuckySearchComponent], @@ -72,7 +75,7 @@ describe('SearchComponent', () => { {provide: Router, useValue: routerStub}, {provide: SearchConfigurationService, useValue: searchConfigServiceStub}, {provide: LuckySearchService, useValue: searchServiceStub}, - + {provide: BitstreamDataService, useValue: bitstreamDataService} ], }) .compileComponents(); @@ -84,61 +87,144 @@ describe('SearchComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + describe('should search items', () => { - it('should show multiple results', () => { - expect(component.showMultipleSearchSection).toEqual(true); - }); + beforeEach(() => { + spyOn(routerStub, 'parseUrl').and.returnValue(urlTree); + }); - it('should display basic search form results', () => { - expect(fixture.debugElement.query(By.css('ds-search-results'))) - .toBeTruthy(); - }); + it('should create', () => { + expect(component).toBeTruthy(); + }); - beforeEach(() => { - fixture = TestBed.createComponent(LuckySearchComponent); - component = fixture.componentInstance; - const firstSearchResult = Object.assign(new SearchResult(), { - indexableObject: Object.assign(new DSpaceObject(), { - id: 'd317835d-7b06-4219-91e2-1191900cb897', - uuid: 'd317835d-7b06-4219-91e2-1191900cb897', - name: 'My first publication', - metadata: { - 'dspace.entity.type': [ - {value: 'Publication'} - ] - } - }) + it('should show multiple results', () => { + expect(component.showMultipleSearchSection).toEqual(true); }); - const data = createSuccessfulRemoteDataObject(createPaginatedList([ - firstSearchResult - ])); - component.resultsRD$.next(data as any); - fixture.detectChanges(); - }); + it('should display basic search form results', () => { + expect(fixture.debugElement.query(By.css('ds-search-results'))) + .toBeTruthy(); + }); - it('should call navigate or router', () => { - expect(routerStub.navigateByUrl).toHaveBeenCalled(); - }); + beforeEach(() => { + fixture = TestBed.createComponent(LuckySearchComponent); + component = fixture.componentInstance; + const firstSearchResult = Object.assign(new SearchResult(), { + indexableObject: Object.assign(new DSpaceObject(), { + id: 'd317835d-7b06-4219-91e2-1191900cb897', + uuid: 'd317835d-7b06-4219-91e2-1191900cb897', + name: 'My first publication', + metadata: { + 'dspace.entity.type': [ + {value: 'Publication'} + ] + } + }) + }); + + const data = createSuccessfulRemoteDataObject(createPaginatedList([ + firstSearchResult + ])); + component.resultsRD$.next(data as any); + fixture.detectChanges(); + }); + it('should call navigate or router', () => { + expect(routerStub.navigateByUrl).toHaveBeenCalled(); + }); - beforeEach(() => { - fixture = TestBed.createComponent(LuckySearchComponent); - component = fixture.componentInstance; - const data = createSuccessfulRemoteDataObject(createPaginatedList([])); - component.resultsRD$.next(data as any); - fixture.detectChanges(); - }); + beforeEach(() => { + fixture = TestBed.createComponent(LuckySearchComponent); + component = fixture.componentInstance; + const data = createSuccessfulRemoteDataObject(createPaginatedList([])); + component.resultsRD$.next(data as any); + fixture.detectChanges(); + }); - it('should not have results', () => { - expect(component.showEmptySearchSection).toEqual(true); + it('should not have results', () => { + expect(component.showEmptySearchSection).toEqual(true); + }); + + it('should display basic search form', () => { + expect(fixture.debugElement.query(By.css('ds-search-form'))) + .toBeTruthy(); + }); }); - it('should display basic search form', () => { - expect(fixture.debugElement.query(By.css('ds-search-form'))) - .toBeTruthy(); + describe('should search bitstreams', () => { + + const bitstreamMetadata = { + 'dc.title': [{ value: 'test.pdf' } as MetadataValue], + 'dc.description': [{ value: 'TestDescription' } as MetadataValue] + } as MetadataMap; + const bitstream = Object.assign( + new Bitstream(), + { _name: 'test.pdf', sizeBytes: 15, uuid: 'fa272dbf-e458-4ad2-868b-b4a27c6eac15', metadata: bitstreamMetadata } + ) as Bitstream; + + beforeEach(() => { + fixture = TestBed.createComponent(LuckySearchComponent); + component = fixture.componentInstance; + + const bitstreamSearchTree = new UrlTree(); + bitstreamSearchTree.queryParams = { + index: 'testIndex', + value: 'testValue', + bitstreamMetadata: 'testMetadata', + bitstreamValue: 'testMetadataValue' + }; + + const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897'; + const firstSearchResult = Object.assign(new SearchResult(), { + indexableObject: Object.assign(new DSpaceObject(), { + id: 'd317835d-7b06-4219-91e2-1191900cb897', + uuid: itemUUID, + name: 'My first publication', + metadata: { + 'dspace.entity.type': [ + { value: 'Publication' } + ] + } + }) + }); + const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult])); + const metadataFilters = [{ metadataName: 'dc.title', metadataValue: 'test.pdf' }] as MetadataFilter[]; + component.bitstreamFilters = metadataFilters; + bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {}).and.returnValue(observableOf({ + state: 'Success', + payload: { page: [bitstream] }, + get hasSucceeded(): boolean { + return true; + } + } as RemoteData>)); + + spyOn(component, 'redirect'); + spyOn(component.bitstreams$, 'next'); + spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree); + + component.resultsRD$.next(data as any); + + fixture.detectChanges(); + }); + + it('should load item bitstreams', () => { + expect(component.bitstreams$.next).toHaveBeenCalledWith([bitstream]); + }); + + it('should redirect to bitstream', () => { + expect(component.redirect).toHaveBeenCalledWith('/bitstreams/fa272dbf-e458-4ad2-868b-b4a27c6eac15/download'); + }); + + it('should return bitstream filename', () => { + expect(component.fileName(bitstream)).toEqual('test.pdf'); + }); + + it('should return bitstream description', () => { + expect(component.getDescription(bitstream)).toEqual('TestDescription'); + }); + + it('should return bitstream file size', () => { + expect(component.getSize(bitstream)).toEqual(15); + }); }); }); diff --git a/src/app/lucky-search/search/lucky-search.component.ts b/src/app/lucky-search/search/lucky-search.component.ts index aaad48dd7bd..f59d41d4abe 100644 --- a/src/app/lucky-search/search/lucky-search.component.ts +++ b/src/app/lucky-search/search/lucky-search.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, mergeMap, Observable, of } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { SearchResult } from '../../shared/search/models/search-result.model'; @@ -9,11 +9,15 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators'; import { SearchFilter } from '../../shared/search/models/search-filter.model'; import { LuckySearchService } from '../lucky-search.service'; import { Router } from '@angular/router'; -import { switchMap, tap } from 'rxjs/operators'; +import { filter, switchMap, tap } from 'rxjs/operators'; import { Context } from '../../core/shared/context.model'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; -import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; import { Item } from '../../core/shared/item.model'; +import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { hasValue } from '../../shared/empty.util'; +import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; @Component({ selector: 'ds-lucky-search', @@ -46,8 +50,17 @@ export class LuckySearchComponent implements OnInit { }; context: Context = Context.ItemPage; + private TITLE_METADATA = 'dc.title'; + private SOURCE_METADATA = 'dc.source'; + private DESCRIPTION_METADATA = 'dc.description'; + + bitstreamFilters: MetadataFilter[]; + bitstreams$ = new BehaviorSubject([]); + item$ = new BehaviorSubject(null); + constructor(private luckySearchService: LuckySearchService, private router: Router, + private bitstreamDataService: BitstreamDataService, public searchConfigService: SearchConfigurationService) { } @@ -55,7 +68,7 @@ export class LuckySearchComponent implements OnInit { this.searchOptions$ = this.getSearchOptions(); this.readResult(); const urlTree = this.router.parseUrl(this.router.url); - if (urlTree.queryParams) { + if (urlTree?.queryParams) { Object.keys(urlTree.queryParams).forEach((key) => { if (key && key === 'index') { this.currentFilter.identifier = urlTree.queryParams[key]; @@ -64,6 +77,7 @@ export class LuckySearchComponent implements OnInit { this.currentFilter.value = urlTree.queryParams[key]; } }); + this.bitstreamFilters = this.parseBitstreamFilters(urlTree.queryParams); } if (!(this.currentFilter.value !== '' && this.currentFilter.identifier !== '')) { this.showEmptySearchSection = true; @@ -87,34 +101,92 @@ export class LuckySearchComponent implements OnInit { } + getDescription(bitstream: Bitstream): string { + return bitstream.firstMetadataValue(this.DESCRIPTION_METADATA); + } + + fileName(bitstream: Bitstream): string { + const title = bitstream.firstMetadataValue(this.TITLE_METADATA); + return hasValue(title) ? title : bitstream.firstMetadataValue(this.SOURCE_METADATA); + } + + getSize(bitstream: Bitstream): number { + return bitstream.sizeBytes; + } + private readResult() { - this.resultsRD$.subscribe((res: RemoteData>>) => { - if (!res) { - return; - } - const total = res.payload.totalElements; - if (total === 0) { - // show message - this.showEmptySearchSection = true; - } else { - if (total === 1) { - const url = getItemPageRoute(res.payload.page[0].indexableObject as Item) ; - // redirect to items detail page - this.redirectToItemsDetailPage(url); - } else { - // show message and all list of results - this.showMultipleSearchSection = true; - } - } - }); + this.resultsRD$ + .pipe( + filter(res => !!res), + mergeMap(res => this.handleResultAndRedirectIfNeeded(res)) + ).subscribe(); + } + + private handleResultAndRedirectIfNeeded(res: RemoteData>>) { + const total = res.payload.totalElements; + if (total === 0) { + this.showEmptySearchSection = true; + } else if (total === 1) { + return this.handleSingleItemResult(res); + } else { + this.showMultipleSearchSection = true; + } + return of(null); + } + + private handleSingleItemResult(res: RemoteData>>) { + const item = res.payload.page[0].indexableObject as Item; + if (this.isBitstreamSearch()) { + return this.loadBitstreamsAndRedirectIfNeeded(item); + } else { + this.redirect(getItemPageRoute(item)); + } + return of(null); + } + + private isBitstreamSearch() { + return this.bitstreamFilters?.length; } private getSearchOptions(): Observable { return this.searchConfigService.paginatedSearchOptions; } - public redirectToItemsDetailPage(url): void { + public redirect(url): void { this.router.navigateByUrl(url, {replaceUrl: true}); } + private parseBitstreamFilters(queryParams): MetadataFilter[] { + const metadataName = queryParams?.bitstreamMetadata; + const metadataValue = queryParams?.bitstreamValue; + if (!!metadataName && !!metadataValue) { + + const metadataNames = Array.isArray(metadataName) ? metadataName : [metadataName]; + const metadataValues = Array.isArray(metadataValue) ? metadataValue : [metadataValue]; + + return metadataNames.map((name, index) => ({ + metadataName: name, + metadataValue: metadataValues[index] + } as MetadataFilter)); + } + return []; + } + + private loadBitstreamsAndRedirectIfNeeded(item: Item): Observable>> { + return this.bitstreamDataService.findByItem(item.uuid, 'ORIGINAL', this.bitstreamFilters, {}) + .pipe( + getFirstSucceededRemoteData(), + tap(bitstreamsResult => { + const bitstreams = bitstreamsResult.payload?.page; + this.bitstreams$.next(bitstreams); + this.item$.next(item); + + if (!bitstreams?.length) { + this.showEmptySearchSection = true; + } else if (bitstreams.length === 1) { + this.redirect(getBitstreamDownloadRoute(bitstreams[0])); + } + }) + ); + } } diff --git a/src/app/shared/mocks/router.mock.ts b/src/app/shared/mocks/router.mock.ts index d16e0d85773..ec08380be87 100644 --- a/src/app/shared/mocks/router.mock.ts +++ b/src/app/shared/mocks/router.mock.ts @@ -38,4 +38,8 @@ export class RouterMock { get url() { return this.routerState.snapshot.url; } + + parseUrl(url: string): UrlTree { + return null; + } } From a75af3e47dd970d3ebd10c07334f4e31abbff921 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 22 Mar 2024 16:48:11 +0100 Subject: [PATCH 06/33] [DSC-1511] Fix issue for which navigating to item page could have lead to and hard redirect of the page when it shouldn't --- src/app/item-page/item-page.resolver.spec.ts | 86 +++++++++++++++----- src/app/item-page/item-page.resolver.ts | 10 ++- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/app/item-page/item-page.resolver.spec.ts b/src/app/item-page/item-page.resolver.spec.ts index 5d91d2177cc..10df9d30133 100644 --- a/src/app/item-page/item-page.resolver.spec.ts +++ b/src/app/item-page/item-page.resolver.spec.ts @@ -7,6 +7,7 @@ import { TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { Router } from '@angular/router'; import { HardRedirectService } from '../core/services/hard-redirect.service'; +import { PLATFORM_ID } from '@angular/core'; describe('ItemPageResolver', () => { beforeEach(() => { @@ -25,6 +26,7 @@ describe('ItemPageResolver', () => { let store; let router; let hardRedirectService: HardRedirectService ; + let platformId; const uuid = '1234-65487-12354-1235'; const item = Object.assign(new Item(), { id: uuid, @@ -58,6 +60,7 @@ describe('ItemPageResolver', () => { beforeEach(() => { router = TestBed.inject(Router); + platformId = TestBed.inject(PLATFORM_ID); itemService = { findById: (id: string) => createSuccessfulRemoteDataObject$(item) } as any; @@ -71,7 +74,7 @@ describe('ItemPageResolver', () => { }); spyOn(router, 'navigateByUrl'); - resolver = new ItemPageResolver(hardRedirectService, itemService, store, router); + resolver = new ItemPageResolver(platformId, hardRedirectService, itemService, store, router); }); it('should resolve a an item from from the item with the url redirect', (done) => { @@ -137,31 +140,74 @@ describe('ItemPageResolver', () => { }); spyOn(router, 'navigateByUrl'); - resolver = new ItemPageResolver(hardRedirectService, itemService, store, router); }); - it('should redirect if it has not the new item url', (done) => { - resolver.resolve({ params: { id: uuid } } as any, { url: '/items/1234-65487-12354-1235/edit' } as any) - .pipe(first()) - .subscribe( - (resolved) => { - expect(hardRedirectService.redirect).toHaveBeenCalledWith('/entities/person/1234-65487-12354-1235/edit', 301); - done(); - } - ); + describe(' and platform is server', () => { + + beforeEach(() => { + platformId = 'server'; + resolver = new ItemPageResolver(platformId, hardRedirectService, itemService, store, router); + }); + + it('should redirect if it has not the new item url', (done) => { + resolver.resolve({ params: { id: uuid } } as any, { url: '/items/1234-65487-12354-1235/edit' } as any) + .pipe(first()) + .subscribe( + (resolved) => { + expect(hardRedirectService.redirect).toHaveBeenCalledWith('/entities/person/1234-65487-12354-1235/edit', 301); + expect(router.navigateByUrl).not.toHaveBeenCalled(); + done(); + } + ); + }); + + it('should not redirect if it has the new item url', (done) => { + resolver.resolve({ params: { id: uuid } } as any, { url: '/entities/person/1234-65487-12354-1235/edit' } as any) + .pipe(first()) + .subscribe( + (resolved) => { + expect(hardRedirectService.redirect).not.toHaveBeenCalled(); + expect(router.navigateByUrl).not.toHaveBeenCalled(); + done(); + } + ); + }); }); - it('should not redirect if it has the new item url', (done) => { - resolver.resolve({ params: { id: uuid } } as any, { url: '/entities/person/1234-65487-12354-1235/edit' } as any) - .pipe(first()) - .subscribe( - (resolved) => { - expect(hardRedirectService.redirect).not.toHaveBeenCalled(); - done(); - } - ); + describe(' and platform is browser', () => { + + beforeEach(() => { + platformId = 'browser'; + resolver = new ItemPageResolver(platformId, hardRedirectService, itemService, store, router); + }); + + it('should redirect if it has not the new item url', (done) => { + resolver.resolve({ params: { id: uuid } } as any, { url: '/items/1234-65487-12354-1235/edit' } as any) + .pipe(first()) + .subscribe( + (resolved) => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/entities/person/1234-65487-12354-1235/edit'); + expect(hardRedirectService.redirect).not.toHaveBeenCalled(); + done(); + } + ); + }); + + it('should not redirect if it has the new item url', (done) => { + resolver.resolve({ params: { id: uuid } } as any, { url: '/entities/person/1234-65487-12354-1235/edit' } as any) + .pipe(first()) + .subscribe( + (resolved) => { + expect(hardRedirectService.redirect).not.toHaveBeenCalled(); + expect(router.navigateByUrl).not.toHaveBeenCalled(); + done(); + } + ); + }); }); + + }); }); diff --git a/src/app/item-page/item-page.resolver.ts b/src/app/item-page/item-page.resolver.ts index c289bae5dad..5a3a37b2d89 100644 --- a/src/app/item-page/item-page.resolver.ts +++ b/src/app/item-page/item-page.resolver.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs'; import { RemoteData } from '../core/data/remote-data'; @@ -10,6 +10,7 @@ import { hasValue, isNotEmpty } from '../shared/empty.util'; import { getItemPageRoute } from './item-page-routing-paths'; import { ItemResolver } from './item.resolver'; import { HardRedirectService } from '../core/services/hard-redirect.service'; +import { isPlatformServer } from '@angular/common'; /** * This class represents a resolver that requests a specific item before the route is activated and will redirect to the @@ -18,6 +19,7 @@ import { HardRedirectService } from '../core/services/hard-redirect.service'; @Injectable() export class ItemPageResolver extends ItemResolver { constructor( + @Inject(PLATFORM_ID) protected platformId: any, protected hardRedirectService: HardRedirectService, protected itemService: ItemDataService, protected store: Store, @@ -55,7 +57,11 @@ export class ItemPageResolver extends ItemResolver { if (!thisRoute.startsWith(itemRoute)) { const itemId = rd.payload.uuid; const subRoute = thisRoute.substring(thisRoute.indexOf(itemId) + itemId.length, thisRoute.length); - this.hardRedirectService.redirect(itemRoute + subRoute, 301); + if (isPlatformServer(this.platformId)) { + this.hardRedirectService.redirect(itemRoute + subRoute, 301); + } else { + this.router.navigateByUrl(itemRoute + subRoute); + } } } } From 1f34e5bfeed2ebbd4076533855bcd1c58d59eb15 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 22 Mar 2024 18:40:56 +0100 Subject: [PATCH 07/33] [DSC-1437] Refactoring --- .../search/lucky-search.component.ts | 157 ++++++++++-------- 1 file changed, 86 insertions(+), 71 deletions(-) diff --git a/src/app/lucky-search/search/lucky-search.component.ts b/src/app/lucky-search/search/lucky-search.component.ts index f59d41d4abe..62d0e3a8e72 100644 --- a/src/app/lucky-search/search/lucky-search.component.ts +++ b/src/app/lucky-search/search/lucky-search.component.ts @@ -1,30 +1,30 @@ -import { Component, OnInit } from '@angular/core'; -import { BehaviorSubject, mergeMap, Observable, of } from 'rxjs'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { BehaviorSubject, mergeMap, Observable, Subject, Subscription } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; -import { getFirstSucceededRemoteData } from '../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../core/shared/operators'; import { SearchFilter } from '../../shared/search/models/search-filter.model'; import { LuckySearchService } from '../lucky-search.service'; -import { Router } from '@angular/router'; -import { filter, switchMap, tap } from 'rxjs/operators'; +import { Params, Router } from '@angular/router'; +import { filter, map, switchMap, tap } from 'rxjs/operators'; import { Context } from '../../core/shared/context.model'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { Item } from '../../core/shared/item.model'; import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service'; -import { getBitstreamDownloadRoute } from '../../app-routing-paths'; import { Bitstream } from '../../core/shared/bitstream.model'; -import { hasValue } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; @Component({ selector: 'ds-lucky-search', templateUrl: './lucky-search.component.html', styleUrls: ['./lucky-search.component.scss'] }) -export class LuckySearchComponent implements OnInit { +export class LuckySearchComponent implements OnInit, OnDestroy { /** * The current search results */ @@ -55,8 +55,10 @@ export class LuckySearchComponent implements OnInit { private DESCRIPTION_METADATA = 'dc.description'; bitstreamFilters: MetadataFilter[]; - bitstreams$ = new BehaviorSubject([]); - item$ = new BehaviorSubject(null); + bitstreams$ = new Subject(); + item$ = new Subject(); + + private readonly subscription = new Subscription(); constructor(private luckySearchService: LuckySearchService, private router: Router, @@ -66,39 +68,54 @@ export class LuckySearchComponent implements OnInit { ngOnInit(): void { this.searchOptions$ = this.getSearchOptions(); + this.handleBitstreamResults(); this.readResult(); - const urlTree = this.router.parseUrl(this.router.url); - if (urlTree?.queryParams) { - Object.keys(urlTree.queryParams).forEach((key) => { + const { queryParams } = this.router.parseUrl(this.router.url); + if (isNotEmpty(queryParams)) { + Object.keys(queryParams).forEach((key) => { if (key && key === 'index') { - this.currentFilter.identifier = urlTree.queryParams[key]; + this.currentFilter.identifier = queryParams[key]; } if (key && key === 'value') { - this.currentFilter.value = urlTree.queryParams[key]; + this.currentFilter.value = queryParams[key]; } }); - this.bitstreamFilters = this.parseBitstreamFilters(urlTree.queryParams); + this.bitstreamFilters = this.parseBitstreamFilters(queryParams); } if (!(this.currentFilter.value !== '' && this.currentFilter.identifier !== '')) { this.showEmptySearchSection = true; return; } - this.searchOptions$.pipe( - switchMap((options: PaginatedSearchOptions) => { - options.filters = [new SearchFilter('f.' + this.currentFilter.identifier, [this.currentFilter.value], 'equals')]; - return this.luckySearchService.sendRequest(options).pipe( - tap((rd: any) => { - if (rd.state && rd.state === 'Error') { - this.showEmptySearchSection = true; - } - }), - getFirstSucceededRemoteData()); - })) - .subscribe((results) => { - return this.resultsRD$.next(results as any); - }); + this.subscription.add( + this.searchOptions$ + .pipe(switchMap((options: PaginatedSearchOptions) => this.getLuckySearchResults(options))) + .subscribe((results) => this.resultsRD$.next(results as any)) + ); + } + private handleBitstreamResults() { + this.subscription.add( + this.bitstreams$.pipe( + filter(bitstreams => isNotEmpty(bitstreams) && bitstreams.length === 1), + map(bitstreams => getBitstreamDownloadRoute(bitstreams[0])) + ).subscribe(bitstreamRoute => this.redirect(bitstreamRoute)) + ); + this.subscription.add( + this.bitstreams$.pipe( + filter(isEmpty) + ).subscribe(bitstreamRoute => this.showEmptySearchSection = true) + ); + } + private getLuckySearchResults(options: PaginatedSearchOptions) { + options.filters = [new SearchFilter('f.' + this.currentFilter.identifier, [this.currentFilter.value], 'equals')]; + return this.luckySearchService.sendRequest(options).pipe( + tap((rd: any) => { + if (rd.state && rd.state === 'Error') { + this.showEmptySearchSection = true; + } + }), + getFirstSucceededRemoteData()); } getDescription(bitstream: Bitstream): string { @@ -115,33 +132,36 @@ export class LuckySearchComponent implements OnInit { } private readResult() { - this.resultsRD$ - .pipe( - filter(res => !!res), - mergeMap(res => this.handleResultAndRedirectIfNeeded(res)) - ).subscribe(); - } - - private handleResultAndRedirectIfNeeded(res: RemoteData>>) { - const total = res.payload.totalElements; - if (total === 0) { - this.showEmptySearchSection = true; - } else if (total === 1) { - return this.handleSingleItemResult(res); - } else { - this.showMultipleSearchSection = true; - } - return of(null); - } - - private handleSingleItemResult(res: RemoteData>>) { - const item = res.payload.page[0].indexableObject as Item; - if (this.isBitstreamSearch()) { - return this.loadBitstreamsAndRedirectIfNeeded(item); - } else { - this.redirect(getItemPageRoute(item)); - } - return of(null); + this.subscription.add( + this.resultsRD$.pipe( + filter(results => results?.payload?.totalElements === 0) + ).subscribe(_ => this.showEmptySearchSection = true) + ); + this.subscription.add( + this.resultsRD$.pipe( + filter(results => + this.isBitstreamSearch() && results?.payload?.totalElements === 1 + ), + map(results => results.payload.page[0].indexableObject as Item), + tap(item => this.item$.next(item)), + mergeMap(item => this.loadBitstreamsAndRedirectIfNeeded(item)) + ).subscribe(results => this.bitstreams$.next(results)) + ); + this.subscription.add( + this.resultsRD$.pipe( + filter(results => + !this.isBitstreamSearch() && results?.payload?.totalElements === 1 + ), + map(results => results.payload.page[0].indexableObject as Item), + tap(item => this.item$.next(item)), + map(item => getItemPageRoute(item)) + ).subscribe(results => this.redirect(results)) + ); + this.subscription.add( + this.resultsRD$.pipe( + filter(results => results?.payload?.totalElements > 1), + ).subscribe(_ => this.showMultipleSearchSection = true) + ); } private isBitstreamSearch() { @@ -156,7 +176,7 @@ export class LuckySearchComponent implements OnInit { this.router.navigateByUrl(url, {replaceUrl: true}); } - private parseBitstreamFilters(queryParams): MetadataFilter[] { + private parseBitstreamFilters(queryParams: Params): MetadataFilter[] { const metadataName = queryParams?.bitstreamMetadata; const metadataValue = queryParams?.bitstreamValue; if (!!metadataName && !!metadataValue) { @@ -172,21 +192,16 @@ export class LuckySearchComponent implements OnInit { return []; } - private loadBitstreamsAndRedirectIfNeeded(item: Item): Observable>> { + private loadBitstreamsAndRedirectIfNeeded(item: Item): Observable { return this.bitstreamDataService.findByItem(item.uuid, 'ORIGINAL', this.bitstreamFilters, {}) .pipe( - getFirstSucceededRemoteData(), - tap(bitstreamsResult => { - const bitstreams = bitstreamsResult.payload?.page; - this.bitstreams$.next(bitstreams); - this.item$.next(item); - - if (!bitstreams?.length) { - this.showEmptySearchSection = true; - } else if (bitstreams.length === 1) { - this.redirect(getBitstreamDownloadRoute(bitstreams[0])); - } - }) + getFirstCompletedRemoteData(), + map(bitstreamsResult => bitstreamsResult.payload?.page) ); } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + } From b41a1c1f5a325f7a3a1247fd97176f4344489fdf Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Thu, 29 Feb 2024 16:12:26 +0100 Subject: [PATCH 08/33] DSC-1603 Last changes and package.json and yarn.lock sortable replaced + lint fix [DURACOM-237] Fix tests [DURACOM-237] - Refactoring based on @angular/cdk/drag-drop [DURACOM-237] - Added DragDropModule which replaced the SortablejsModule inside form.module.ts --- package.json | 2 -- .../shared/form/chips/chips.component.html | 22 +++++++--------- .../shared/form/chips/chips.component.scss | 26 +++++++++++++++---- .../shared/form/chips/chips.component.spec.ts | 10 +++---- src/app/shared/form/chips/chips.component.ts | 25 ++++++------------ src/app/shared/form/form.module.ts | 4 +-- yarn.lock | 12 --------- 7 files changed, 45 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index a55594240da..d9ad11580cd 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,6 @@ "ng2-nouislider": "^2.0.0", "ngx-infinite-scroll": "^15.0.0", "ngx-pagination": "6.0.3", - "ngx-sortablejs": "^11.1.0", "ngx-ui-switch": "^14.0.3", "nouislider": "^15.7.1", "pem": "1.14.7", @@ -149,7 +148,6 @@ "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "sanitize-html": "^2.10.0", - "sortablejs": "1.15.0", "uuid": "^8.3.2", "webfontloader": "1.6.28", "zone.js": "~0.11.5" diff --git a/src/app/shared/form/chips/chips.component.html b/src/app/shared/form/chips/chips.component.html index 95e41b2725a..c6f47f37ec0 100644 --- a/src/app/shared/form/chips/chips.component.html +++ b/src/app/shared/form/chips/chips.component.html @@ -4,29 +4,28 @@
- +
+
+
{{ 'person.page.orcid.products-references'| translate }}
+
+
+
+ + {{ 'person.page.orcid.synchronization-mode-product-message' | translate}} + +
+
+
+ + +
+
+
+
+
+
{{ 'person.page.orcid.funding-preferences'| translate }}
diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts index 79b307da88b..a3b75b98d02 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts @@ -40,6 +40,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ currentSyncPublications: string; + /** + * The current synchronization mode for product + */ + currentSyncProduct: string; + /** * The current synchronization mode for funding */ @@ -55,6 +60,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ syncPublicationOptions: { value: string, label: string }[]; + /** + * The synchronization options for products + */ + syncProductOptions: { value: string, label: string }[]; + /** * The synchronization options for funding */ @@ -98,6 +108,14 @@ export class OrcidSyncSettingsComponent implements OnInit { }; }); + this.syncProductOptions = ['DISABLED', 'ALL'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-products.' + value.toLowerCase(), + value: value, + }; + }); + this.syncFundingOptions = ['DISABLED', 'ALL'] .map((value) => { return { @@ -119,6 +137,7 @@ export class OrcidSyncSettingsComponent implements OnInit { this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH', 'MANUAL'], 'MANUAL'); this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncProduct = this.getCurrentPreference('dspace.orcid.sync-products', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncFunding = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); } @@ -131,6 +150,7 @@ export class OrcidSyncSettingsComponent implements OnInit { const operations: Operation[] = []; this.fillOperationsFor(operations, '/orcid/mode', form.value.syncMode); this.fillOperationsFor(operations, '/orcid/publications', form.value.syncPublications); + this.fillOperationsFor(operations, '/orcid/products', form.value.syncProducts); this.fillOperationsFor(operations, '/orcid/fundings', form.value.syncFundings); const syncProfileValue = this.syncProfileOptions diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e1fa6450c7c..c2bf5826294 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6900,6 +6900,8 @@ "person.page.orcid.funding-preferences": "Funding preferences", + "person.page.orcid.product-preferences": "Product preferences", + "person.page.orcid.publications-preferences": "Publication preferences", "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", @@ -6930,6 +6932,14 @@ "person.page.orcid.sync-publications.disabled": "Disabled", + "person.page.orcid.sync-products.all": "All products", + + "person.page.orcid.sync-products.mine": "My products", + + "person.page.orcid.sync-products.my_selected": "Selected products", + + "person.page.orcid.sync-products.disabled": "Disabled", + "person.page.orcid.sync-queue.discard": "Discard the change and do not synchronize with the ORCID registry", "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", @@ -6968,6 +6978,8 @@ "person.page.orcid.sync-queue.tooltip.publication": "Publication", + "person.page.orcid.sync-queue.tooltip.product": "Product", + "person.page.orcid.sync-queue.tooltip.project": "Project", "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", From 56bbf2611524e26cef2833f6d1fb60f69cbb8a87 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Wed, 21 Feb 2024 12:47:45 +0100 Subject: [PATCH 11/33] missing orcid product message --- src/assets/i18n/en.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c2bf5826294..fa3c7ff5899 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7062,6 +7062,8 @@ "person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-product-message": "Select whether to send your linked Product entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", "person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", From fbf22ebd456ee510cc29d6a44da11c70b6484d28 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 22 Feb 2024 14:19:52 +0100 Subject: [PATCH 12/33] component test for new product orcid sync-setting --- .../orcid-sync-settings.component.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts index f2fa9d2440b..110b0ae3cac 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts @@ -168,10 +168,12 @@ describe('OrcidSyncSettingsComponent test suite', () => { it('should create cards properly', () => { const modes = fixture.debugElement.query(By.css('[data-test="sync-mode"]')); const publication = fixture.debugElement.query(By.css('[data-test="sync-mode-publication"]')); + const product = fixture.debugElement.query(By.css('[data-test="sync-mode-product"]')); const funding = fixture.debugElement.query(By.css('[data-test="sync-mode-funding"]')); const preferences = fixture.debugElement.query(By.css('[data-test="profile-preferences"]')); expect(modes).toBeTruthy(); expect(publication).toBeTruthy(); + expect(product).toBeTruthy(); expect(funding).toBeTruthy(); expect(preferences).toBeTruthy(); }); @@ -179,6 +181,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { it('should init sync modes properly', () => { expect(comp.currentSyncMode).toBe('MANUAL'); expect(comp.currentSyncPublications).toBe('ALL'); + expect(comp.currentSyncProduct).toBe('DISABLED'); expect(comp.currentSyncFunding).toBe('DISABLED'); }); @@ -189,6 +192,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { formGroup = new UntypedFormGroup({ syncMode: new UntypedFormControl('MANUAL'), syncFundings: new UntypedFormControl('ALL'), + syncProducts: new UntypedFormControl('ALL'), syncPublications: new UntypedFormControl('ALL'), syncProfile_BIOGRAPHICAL: new UntypedFormControl(true), syncProfile_IDENTIFIERS: new UntypedFormControl(true), @@ -208,6 +212,10 @@ describe('OrcidSyncSettingsComponent test suite', () => { path: '/orcid/publications', op: 'replace', value: 'ALL' + }, { + path: '/orcid/products', + op: 'replace', + value: 'ALL' }, { path: '/orcid/fundings', op: 'replace', From 9d3e20f097933dc8162aa571602105463d2d84c2 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Tue, 26 Mar 2024 13:21:13 +0100 Subject: [PATCH 13/33] [DSC-1454] Refactors new features into methods --- .../full/full-item-page.component.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 36896754a35..eece5503f05 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -73,13 +73,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, map((rd: RemoteData) => rd.payload), filter((item: Item) => hasValue(item)), map((item: Item) => item.metadata), - tap((metadataMap: MetadataMap) => { - const metadataMapLimit: Map = new Map(); - Object.keys(metadataMap).forEach((key: string) => { - metadataMapLimit.set(key, this.limitSize); - }); - this.metadataMapLimit$.next(metadataMapLimit); - }) + tap((metadataMap: MetadataMap) => this.nextMetadataMapLimit(metadataMap)) ); this.subs.push(this.route.data.subscribe((data: Data) => { @@ -99,9 +93,17 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } - increaseLimit(key: string) { - const tmpMap: Map = this.metadataMapLimit$.value; - tmpMap.set(key, tmpMap.get(key) + this.limitSize); - this.metadataMapLimit$.next(tmpMap); + protected increaseLimit(metadataKey: string) { + const newMetadataMap: Map = new Map(this.metadataMapLimit$.value); + const newMetadataSize = newMetadataMap.get(metadataKey) + this.limitSize; + newMetadataMap.set(metadataKey, newMetadataSize); + this.metadataMapLimit$.next(newMetadataMap); + } + + protected nextMetadataMapLimit(metadataMap: MetadataMap) { + const metadataMapLimit: Map = new Map(this.metadataMapLimit$.value); + Object.keys(metadataMap).forEach((key: string) => metadataMapLimit.set(key, this.limitSize)); + this.metadataMapLimit$.next(metadataMapLimit); } + } From 92065412c573b98f39dad986b1711c8e420d2c1a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 13:29:09 +0100 Subject: [PATCH 14/33] [DSC-1588] Fix i18n labels --- .../orcid-sync-settings/orcid-sync-settings.component.html | 2 +- src/assets/i18n/en.json5 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html index 6f46e3c9bf2..479ed7b2e04 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html @@ -51,7 +51,7 @@

{{'person.orcid.sync.setting' | translate}}

-
{{ 'person.page.orcid.products-references'| translate }}
+
{{ 'person.page.orcid.products-preferences'| translate }}
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fa3c7ff5899..c38aa246a04 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6900,7 +6900,7 @@ "person.page.orcid.funding-preferences": "Funding preferences", - "person.page.orcid.product-preferences": "Product preferences", + "person.page.orcid.products-preferences": "Product preferences", "person.page.orcid.publications-preferences": "Publication preferences", From 51d096e916c1e095cb37f1929ad335075e57e0e2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 14:50:44 +0100 Subject: [PATCH 15/33] [DSC-1609] Run pipeline on self.hosted --- bitbucket-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 3e36e21f1f0..dcc052cf64e 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -5,6 +5,7 @@ definitions: steps: - step: &unittest-code-checks name: test-code-checks + runs-on: self.hosted image: name: cypress/browsers:node18.12.0-chrome107 run-as-user: 1000 From 8610f0bd91e11fa53f70b2d488b127c9a254d418 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Wed, 21 Feb 2024 12:40:18 +0100 Subject: [PATCH 16/33] orcid sync settings for product entity --- .../orcid-queue/orcid-queue.component.ts | 2 ++ .../orcid-sync-settings.component.html | 23 +++++++++++++++++++ .../orcid-sync-settings.component.ts | 20 ++++++++++++++++ src/assets/i18n/en.json5 | 12 ++++++++++ 4 files changed, 57 insertions(+) diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts index 74cff399540..2ce25858ae7 100644 --- a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts +++ b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts @@ -120,6 +120,8 @@ export class OrcidQueueComponent implements OnInit, OnDestroy { switch (orcidQueue.recordType.toLowerCase()) { case 'publication': return 'fas fa-book'; + case 'product': + return 'fas fa-database'; case 'funding': return 'fa fa-wallet'; case 'project': diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html index ee9a15268a5..6f46e3c9bf2 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html @@ -49,6 +49,29 @@

{{'person.orcid.sync.setting' | translate}}

+
+
+
{{ 'person.page.orcid.products-references'| translate }}
+
+
+
+ + {{ 'person.page.orcid.synchronization-mode-product-message' | translate}} + +
+
+
+ + +
+
+
+
+
+
{{ 'person.page.orcid.funding-preferences'| translate }}
diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts index 79b307da88b..a3b75b98d02 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts @@ -40,6 +40,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ currentSyncPublications: string; + /** + * The current synchronization mode for product + */ + currentSyncProduct: string; + /** * The current synchronization mode for funding */ @@ -55,6 +60,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ syncPublicationOptions: { value: string, label: string }[]; + /** + * The synchronization options for products + */ + syncProductOptions: { value: string, label: string }[]; + /** * The synchronization options for funding */ @@ -98,6 +108,14 @@ export class OrcidSyncSettingsComponent implements OnInit { }; }); + this.syncProductOptions = ['DISABLED', 'ALL'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-products.' + value.toLowerCase(), + value: value, + }; + }); + this.syncFundingOptions = ['DISABLED', 'ALL'] .map((value) => { return { @@ -119,6 +137,7 @@ export class OrcidSyncSettingsComponent implements OnInit { this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH', 'MANUAL'], 'MANUAL'); this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); + this.currentSyncProduct = this.getCurrentPreference('dspace.orcid.sync-products', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncFunding = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); } @@ -131,6 +150,7 @@ export class OrcidSyncSettingsComponent implements OnInit { const operations: Operation[] = []; this.fillOperationsFor(operations, '/orcid/mode', form.value.syncMode); this.fillOperationsFor(operations, '/orcid/publications', form.value.syncPublications); + this.fillOperationsFor(operations, '/orcid/products', form.value.syncProducts); this.fillOperationsFor(operations, '/orcid/fundings', form.value.syncFundings); const syncProfileValue = this.syncProfileOptions diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e1fa6450c7c..c2bf5826294 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6900,6 +6900,8 @@ "person.page.orcid.funding-preferences": "Funding preferences", + "person.page.orcid.product-preferences": "Product preferences", + "person.page.orcid.publications-preferences": "Publication preferences", "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", @@ -6930,6 +6932,14 @@ "person.page.orcid.sync-publications.disabled": "Disabled", + "person.page.orcid.sync-products.all": "All products", + + "person.page.orcid.sync-products.mine": "My products", + + "person.page.orcid.sync-products.my_selected": "Selected products", + + "person.page.orcid.sync-products.disabled": "Disabled", + "person.page.orcid.sync-queue.discard": "Discard the change and do not synchronize with the ORCID registry", "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", @@ -6968,6 +6978,8 @@ "person.page.orcid.sync-queue.tooltip.publication": "Publication", + "person.page.orcid.sync-queue.tooltip.product": "Product", + "person.page.orcid.sync-queue.tooltip.project": "Project", "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", From 8f56ed9c540543cbb725e554c8ab91107ec85e66 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Wed, 21 Feb 2024 12:47:45 +0100 Subject: [PATCH 17/33] missing orcid product message --- src/assets/i18n/en.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c2bf5826294..fa3c7ff5899 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7062,6 +7062,8 @@ "person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-product-message": "Select whether to send your linked Product entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", "person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", From 9e074ce7acb7794b2b124d7574d45de9ca3e463b Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Thu, 22 Feb 2024 14:19:52 +0100 Subject: [PATCH 18/33] component test for new product orcid sync-setting --- .../orcid-sync-settings.component.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts index f2fa9d2440b..110b0ae3cac 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts @@ -168,10 +168,12 @@ describe('OrcidSyncSettingsComponent test suite', () => { it('should create cards properly', () => { const modes = fixture.debugElement.query(By.css('[data-test="sync-mode"]')); const publication = fixture.debugElement.query(By.css('[data-test="sync-mode-publication"]')); + const product = fixture.debugElement.query(By.css('[data-test="sync-mode-product"]')); const funding = fixture.debugElement.query(By.css('[data-test="sync-mode-funding"]')); const preferences = fixture.debugElement.query(By.css('[data-test="profile-preferences"]')); expect(modes).toBeTruthy(); expect(publication).toBeTruthy(); + expect(product).toBeTruthy(); expect(funding).toBeTruthy(); expect(preferences).toBeTruthy(); }); @@ -179,6 +181,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { it('should init sync modes properly', () => { expect(comp.currentSyncMode).toBe('MANUAL'); expect(comp.currentSyncPublications).toBe('ALL'); + expect(comp.currentSyncProduct).toBe('DISABLED'); expect(comp.currentSyncFunding).toBe('DISABLED'); }); @@ -189,6 +192,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { formGroup = new UntypedFormGroup({ syncMode: new UntypedFormControl('MANUAL'), syncFundings: new UntypedFormControl('ALL'), + syncProducts: new UntypedFormControl('ALL'), syncPublications: new UntypedFormControl('ALL'), syncProfile_BIOGRAPHICAL: new UntypedFormControl(true), syncProfile_IDENTIFIERS: new UntypedFormControl(true), @@ -208,6 +212,10 @@ describe('OrcidSyncSettingsComponent test suite', () => { path: '/orcid/publications', op: 'replace', value: 'ALL' + }, { + path: '/orcid/products', + op: 'replace', + value: 'ALL' }, { path: '/orcid/fundings', op: 'replace', From 957500b7ac9e644c084e41f2fba0688037c762a2 Mon Sep 17 00:00:00 2001 From: "Gantner, Florian Klaus" Date: Tue, 27 Feb 2024 11:25:14 +0100 Subject: [PATCH 19/33] orcid sync settings for patent entity --- .../orcid-queue/orcid-queue.component.ts | 2 ++ .../orcid-sync-settings.component.html | 22 +++++++++++++++++++ .../orcid-sync-settings.component.spec.ts | 15 +++++++++++++ .../orcid-sync-settings.component.ts | 20 +++++++++++++++++ src/assets/i18n/en.json5 | 14 ++++++++++++ 5 files changed, 73 insertions(+) diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts index 2ce25858ae7..ba3987153b7 100644 --- a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts +++ b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts @@ -126,6 +126,8 @@ export class OrcidQueueComponent implements OnInit, OnDestroy { return 'fa fa-wallet'; case 'project': return 'fas fa-wallet'; + case 'patent': + return 'fas fa-file'; case 'education': return 'fas fa-school'; case 'affiliation': diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html index 6f46e3c9bf2..b54705594f9 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.html @@ -94,6 +94,28 @@

{{'person.orcid.sync.setting' | translate}}

+
+
+
{{ 'person.page.orcid.patent-preferences'| translate }}
+
+
+
+ + {{ 'person.page.orcid.synchronization-mode-patent-message' | translate}} + +
+
+
+ + +
+
+
+
+
+
{{ 'person.page.orcid.profile-preferences'| translate }}
diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts index 110b0ae3cac..aa16e4c54c7 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.spec.ts @@ -117,6 +117,13 @@ describe('OrcidSyncSettingsComponent test suite', () => { 'confidence': -1, 'place': 0 }], + 'dspace.orcid.sync-patents': [{ + 'value': 'DISABLED', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + }], 'person.identifier.orcid': [{ 'value': 'orcid-id', 'language': null, @@ -167,6 +174,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { it('should create cards properly', () => { const modes = fixture.debugElement.query(By.css('[data-test="sync-mode"]')); + const patent = fixture.debugElement.query(By.css('[data-test="sync-mode-patent"]')); const publication = fixture.debugElement.query(By.css('[data-test="sync-mode-publication"]')); const product = fixture.debugElement.query(By.css('[data-test="sync-mode-product"]')); const funding = fixture.debugElement.query(By.css('[data-test="sync-mode-funding"]')); @@ -174,6 +182,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { expect(modes).toBeTruthy(); expect(publication).toBeTruthy(); expect(product).toBeTruthy(); + expect(patent).toBeTruthy(); expect(funding).toBeTruthy(); expect(preferences).toBeTruthy(); }); @@ -183,6 +192,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { expect(comp.currentSyncPublications).toBe('ALL'); expect(comp.currentSyncProduct).toBe('DISABLED'); expect(comp.currentSyncFunding).toBe('DISABLED'); + expect(comp.currentSyncPatent).toBe('DISABLED'); }); describe('form submit', () => { @@ -193,6 +203,7 @@ describe('OrcidSyncSettingsComponent test suite', () => { syncMode: new UntypedFormControl('MANUAL'), syncFundings: new UntypedFormControl('ALL'), syncProducts: new UntypedFormControl('ALL'), + syncPatents: new UntypedFormControl('ALL'), syncPublications: new UntypedFormControl('ALL'), syncProfile_BIOGRAPHICAL: new UntypedFormControl(true), syncProfile_IDENTIFIERS: new UntypedFormControl(true), @@ -208,6 +219,10 @@ describe('OrcidSyncSettingsComponent test suite', () => { path: '/orcid/mode', op: 'replace', value: 'MANUAL' + }, { + path: '/orcid/patents', + op: 'replace', + value: 'ALL' }, { path: '/orcid/publications', op: 'replace', diff --git a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts index a3b75b98d02..7dad499dbf0 100644 --- a/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts +++ b/src/app/item-page/orcid-page/orcid-sync-settings/orcid-sync-settings.component.ts @@ -35,6 +35,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ currentSyncMode: string; + /** + * The current synchronization mode for patents + */ + currentSyncPatent: string; + /** * The current synchronization mode for publications */ @@ -55,6 +60,11 @@ export class OrcidSyncSettingsComponent implements OnInit { */ syncModes: { value: string, label: string }[]; + /** + * The synchronization options for patents + */ + syncPatentOptions: { value: string, label: string }[]; + /** * The synchronization options for publications */ @@ -116,6 +126,14 @@ export class OrcidSyncSettingsComponent implements OnInit { }; }); + this.syncPatentOptions = ['DISABLED', 'ALL'] + .map((value) => { + return { + label: this.messagePrefix + '.sync-patents.' + value.toLowerCase(), + value: value, + }; + }); + this.syncFundingOptions = ['DISABLED', 'ALL'] .map((value) => { return { @@ -136,6 +154,7 @@ export class OrcidSyncSettingsComponent implements OnInit { }); this.currentSyncMode = this.getCurrentPreference('dspace.orcid.sync-mode', ['BATCH', 'MANUAL'], 'MANUAL'); + this.currentSyncPatent = this.getCurrentPreference('dspace.orcid.sync-patents', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncPublications = this.getCurrentPreference('dspace.orcid.sync-publications', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncProduct = this.getCurrentPreference('dspace.orcid.sync-products', ['DISABLED', 'ALL'], 'DISABLED'); this.currentSyncFunding = this.getCurrentPreference('dspace.orcid.sync-fundings', ['DISABLED', 'ALL'], 'DISABLED'); @@ -149,6 +168,7 @@ export class OrcidSyncSettingsComponent implements OnInit { onSubmit(form: UntypedFormGroup): void { const operations: Operation[] = []; this.fillOperationsFor(operations, '/orcid/mode', form.value.syncMode); + this.fillOperationsFor(operations, '/orcid/patents', form.value.syncPatents); this.fillOperationsFor(operations, '/orcid/publications', form.value.syncPublications); this.fillOperationsFor(operations, '/orcid/products', form.value.syncProducts); this.fillOperationsFor(operations, '/orcid/fundings', form.value.syncFundings); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fa3c7ff5899..7c891b49840 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6902,6 +6902,8 @@ "person.page.orcid.product-preferences": "Product preferences", + "person.page.orcid.patent-preferences": "Patent preferences", + "person.page.orcid.publications-preferences": "Publication preferences", "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", @@ -6924,6 +6926,14 @@ "person.page.orcid.sync-fundings.disabled": "Disabled", + "person.page.orcid.sync-patents.all": "All patents", + + "person.page.orcid.sync-patents.mine": "My patents", + + "person.page.orcid.sync-patents.my_selected": "Selected patents", + + "person.page.orcid.sync-patents.disabled": "Disabled", + "person.page.orcid.sync-publications.all": "All publications", "person.page.orcid.sync-publications.mine": "My publications", @@ -6976,6 +6986,8 @@ "person.page.orcid.sync-queue.tooltip.delete": "Remove this entry from the ORCID registry", + "person.page.orcid.sync-queue.tooltip.patent": "Patent", + "person.page.orcid.sync-queue.tooltip.publication": "Publication", "person.page.orcid.sync-queue.tooltip.product": "Product", @@ -7060,6 +7072,8 @@ "person.page.orcid.synchronization-mode-funding-message": "Select whether to send your linked Project entities to your ORCID record's list of funding information.", + "person.page.orcid.synchronization-mode-patent-message": "Select whether to send your linked Patent entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", "person.page.orcid.synchronization-mode-product-message": "Select whether to send your linked Product entities to your ORCID record's list of works.", From 351cb3caac632d3211276bf62c8f2f6a6c65b5f0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 15:19:04 +0100 Subject: [PATCH 20/33] [DSC-1609] Configure image as service --- bitbucket-pipelines.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index dcc052cf64e..a0682e413c0 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -2,13 +2,17 @@ options: runs-on: ubuntu-latest definitions: + services: + docker: # can only be used with a self-hosted runner + image: + name: cypress/browsers:node18.12.0-chrome107 + run-as-user: 1000 steps: - step: &unittest-code-checks name: test-code-checks runs-on: self.hosted - image: - name: cypress/browsers:node18.12.0-chrome107 - run-as-user: 1000 + services: + - docker size: 2x caches: - node From 4ae91c4b3484e3b08bbf86dc3971b2de743c1e38 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 16:59:15 +0100 Subject: [PATCH 21/33] [DSC-1609] Test without options property --- bitbucket-pipelines.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index a0682e413c0..5eeba9e5cef 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -1,18 +1,21 @@ -options: - runs-on: ubuntu-latest +#options: +# runs-on: ubuntu-latest definitions: services: - docker: # can only be used with a self-hosted runner + node-image: + type: docker image: name: cypress/browsers:node18.12.0-chrome107 run-as-user: 1000 + steps: - step: &unittest-code-checks name: test-code-checks - runs-on: self.hosted + runs-on: + - 'self.hosted' services: - - docker + - node-image size: 2x caches: - node From 4937622784058b4f01bc7dfb6d5e6a289f493cab Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 17:09:29 +0100 Subject: [PATCH 22/33] [DSC-1609] WIP Test without options property --- bitbucket-pipelines.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 5eeba9e5cef..e53e49654bd 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -2,20 +2,13 @@ # runs-on: ubuntu-latest definitions: - services: - node-image: - type: docker - image: - name: cypress/browsers:node18.12.0-chrome107 - run-as-user: 1000 - steps: - step: &unittest-code-checks name: test-code-checks - runs-on: - - 'self.hosted' - services: - - node-image + runs-on: self.hosted + image: + name: cypress/browsers:node18.12.0-chrome107 + run-as-user: 1000 size: 2x caches: - node From faec1c58bd7de067288b639a7627b167d761c377 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 18:40:13 +0100 Subject: [PATCH 23/33] [DSC-1609] WIP try to use options --- bitbucket-pipelines.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index e53e49654bd..54d91c7e620 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -1,11 +1,10 @@ -#options: -# runs-on: ubuntu-latest +options: + runs-on: self.hosted definitions: steps: - step: &unittest-code-checks name: test-code-checks - runs-on: self.hosted image: name: cypress/browsers:node18.12.0-chrome107 run-as-user: 1000 From d31cd7d8c2999f481742c1b8f23554964f7497ce Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 26 Mar 2024 20:37:19 +0100 Subject: [PATCH 24/33] [DSC-1609] Fix memory problem during pipeline --- bitbucket-pipelines.yml | 2 +- package.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 54d91c7e620..921b4c9b034 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -15,7 +15,7 @@ definitions: - yarn install --frozen-lockfile - yarn run lint --quiet - yarn run check-circ-deps - - yarn run build:prod + - yarn run build:prod:ci - yarn run test:headless pipelines: diff --git a/package.json b/package.json index a55594240da..d5329923ab3 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,11 @@ "build:stats": "ng build --stats-json", "build:ci": "ng config cli.cache.environment ci && yarn run build:ssr", "build:prod": "cross-env NODE_ENV=production yarn run build:ssr", + "build:prod:ci": "cross-env NODE_ENV=production yarn run build:ssr:ci", "build:ssr": "npm run ng-high-memory -- build --configuration production && npm run ng-high-memory -- run dspace-angular:server:production", + "build:ssr:ci": "npm run ng-mid-memory -- build --configuration production && npm run ng-mid-memory -- run dspace-angular:server:production", "ng-high-memory": "node --max-old-space-size=8192 node_modules/@angular/cli/bin/ng", + "ng-mid-memory": "node --max-old-space-size=4096 node_modules/@angular/cli/bin/ng", "test": "npm run ng-high-memory -- test --source-map=true --watch=false --configuration test", "test:watch": "nodemon --exec \"npm run ng-high-memory -- test --source-map=true --watch=true --configuration test\"", "test:headless": "npm run ng-high-memory -- test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage", From 171c78b4d2a9c4d4426aaab51c0dbd7812e6a200 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 29 Nov 2022 15:11:33 +0100 Subject: [PATCH 25/33] [DSC-758] Need a parameter on Angular Side to force Language via URL --- src/app/core/locale/locale.service.spec.ts | 86 ++++++++++++++------ src/app/core/locale/locale.service.ts | 12 +++ src/app/shared/testing/route-service.stub.ts | 2 +- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/app/core/locale/locale.service.spec.ts b/src/app/core/locale/locale.service.spec.ts index 39356fdf970..4384d08d91c 100644 --- a/src/app/core/locale/locale.service.spec.ts +++ b/src/app/core/locale/locale.service.spec.ts @@ -10,8 +10,9 @@ import { AuthService } from '../auth/auth.service'; import { NativeWindowRef } from '../services/window.service'; import { RouteService } from '../services/route.service'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; +import { of as observableOf } from 'rxjs'; -describe('LocaleService test suite', () => { +fdescribe('LocaleService test suite', () => { let service: LocaleService; let serviceAsAny: any; let cookieService: CookieService; @@ -22,13 +23,25 @@ describe('LocaleService test suite', () => { let authService; let routeService; let document; + let spyOnGetLanguage; authService = jasmine.createSpyObj('AuthService', { isAuthenticated: jasmine.createSpy('isAuthenticated'), isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded') }); - const langList = ['en', 'xx', 'de']; + const translateServiceStub: any = { + getLangs: () => { + return langList; + }, + getBrowserLang: () => { + return langList; + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + use: (param: string) => {} + }; + + const langList = ['en', 'it', 'de']; beforeEach(waitForAsync(() => { return TestBed.configureTestingModule({ @@ -44,6 +57,7 @@ describe('LocaleService test suite', () => { { provide: CookieService, useValue: new CookieServiceMock() }, { provide: AuthService, userValue: authService }, { provide: RouteService, useValue: routeServiceStub }, + { provide: TranslateService, useValue: translateServiceStub }, { provide: Document, useValue: document }, ] }); @@ -59,36 +73,29 @@ describe('LocaleService test suite', () => { serviceAsAny = service; spyOnGet = spyOn(cookieService, 'get'); spyOnSet = spyOn(cookieService, 'set'); + spyOnGetLanguage = spyOn(routeService, 'getQueryParameterValue').withArgs('lang'); }); describe('getCurrentLanguageCode', () => { - beforeEach(() => { - spyOn(translateService, 'getLangs').and.returnValue(langList); - }); - - it('should return the language saved on cookie if it\'s a valid & active language', () => { + it('should return language saved on cookie', () => { spyOnGet.and.returnValue('de'); expect(service.getCurrentLanguageCode()).toBe('de'); }); - it('should return the default language if the cookie language is disabled', () => { - spyOnGet.and.returnValue('disabled'); - expect(service.getCurrentLanguageCode()).toBe('en'); - }); - - it('should return the default language if the cookie language does not exist', () => { - spyOnGet.and.returnValue('does-not-exist'); - expect(service.getCurrentLanguageCode()).toBe('en'); - }); + describe('', () => { + beforeEach(() => { + spyOn(translateService, 'getLangs').and.returnValue(langList); + }); - it('should return language from browser setting', () => { - spyOn(translateService, 'getBrowserLang').and.returnValue('xx'); - expect(service.getCurrentLanguageCode()).toBe('xx'); - }); + it('should return language from browser setting', () => { + spyOn(translateService, 'getBrowserLang').and.returnValue('it'); + expect(service.getCurrentLanguageCode()).toBe('it'); + }); - it('should return default language from config', () => { - spyOn(translateService, 'getBrowserLang').and.returnValue('fr'); - expect(service.getCurrentLanguageCode()).toBe('en'); + it('should return default language from config', () => { + spyOn(translateService, 'getBrowserLang').and.returnValue('fr'); + expect(service.getCurrentLanguageCode()).toBe('en'); + }); }); }); @@ -114,9 +121,9 @@ describe('LocaleService test suite', () => { }); it('should set the given language', () => { - service.setCurrentLanguageCode('xx'); - expect(translateService.use).toHaveBeenCalledWith('xx'); - expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('xx'); + service.setCurrentLanguageCode('it'); + expect(translateService.use).toHaveBeenCalledWith('it'); + expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('it'); }); it('should set the current language', () => { @@ -131,11 +138,36 @@ describe('LocaleService test suite', () => { service.setCurrentLanguageCode(); expect((service as any).document.documentElement.lang).toEqual('es'); }); + + describe('should set language on init', () => { + beforeEach(() => { + spyOn(translateService, 'getLangs').and.returnValue(langList); + spyOn(service, 'setCurrentLanguageCode'); + }); + describe('whith correct lang query param ', () => { + beforeEach(() => { + spyOnGetLanguage.and.returnValue(observableOf('en')); + service.initDefaults(); + }); + it('should set correct lang', () => { + expect(service.setCurrentLanguageCode).toHaveBeenCalledWith('en'); + }); + }); + describe('whith wrong lang query param ', () => { + beforeEach(() => { + spyOnGetLanguage.and.returnValue(observableOf('abcd')); + service.initDefaults(); + }); + it('should not set lang', () => { + expect(service.setCurrentLanguageCode).not.toHaveBeenCalled(); + }); + }); + }); }); describe('', () => { it('should set quality to current language list', () => { - const langListWithQuality = ['en;q=1', 'xx;q=0.9', 'de;q=0.8']; + const langListWithQuality = ['en;q=1', 'it;q=0.9', 'de;q=0.8']; spyOn(service, 'setQuality').and.returnValue(langListWithQuality); service.setQuality(langList, LANG_ORIGIN.BROWSER, false); expect(service.setQuality).toHaveBeenCalledWith(langList, LANG_ORIGIN.BROWSER, false); diff --git a/src/app/core/locale/locale.service.ts b/src/app/core/locale/locale.service.ts index 5c080d8c16c..2cb94314256 100644 --- a/src/app/core/locale/locale.service.ts +++ b/src/app/core/locale/locale.service.ts @@ -43,6 +43,18 @@ export class LocaleService { protected routeService: RouteService, @Inject(DOCUMENT) protected document: any ) { + this.initDefaults(); + } + + /** + * Initialize the language from query params + */ + initDefaults() { + this.routeService.getQueryParameterValue('lang').subscribe(lang => { + if (lang && this.translate.getLangs().some(language => language === lang)) { + this.setCurrentLanguageCode(lang); + } + }); } /** diff --git a/src/app/shared/testing/route-service.stub.ts b/src/app/shared/testing/route-service.stub.ts index 8384c3efbcc..a31a058dd12 100644 --- a/src/app/shared/testing/route-service.stub.ts +++ b/src/app/shared/testing/route-service.stub.ts @@ -23,7 +23,7 @@ export const routeServiceStub: any = { getQueryParamMap: () => { return observableOf(new Map()); }, - getQueryParameterValue: () => { + getQueryParameterValue: (lang?: string) => { return observableOf({}); }, getRouteParameterValue: (param) => { From 8f1627cf4226db429d4a36a59c2541cfb4cdfb31 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 12 Dec 2023 15:42:38 +0100 Subject: [PATCH 26/33] [DSC-758] fix broken tests --- src/app/core/locale/locale.service.spec.ts | 232 +++++++++++---------- 1 file changed, 118 insertions(+), 114 deletions(-) diff --git a/src/app/core/locale/locale.service.spec.ts b/src/app/core/locale/locale.service.spec.ts index 4384d08d91c..6195774a51d 100644 --- a/src/app/core/locale/locale.service.spec.ts +++ b/src/app/core/locale/locale.service.spec.ts @@ -25,10 +25,6 @@ fdescribe('LocaleService test suite', () => { let document; let spyOnGetLanguage; - authService = jasmine.createSpyObj('AuthService', { - isAuthenticated: jasmine.createSpy('isAuthenticated'), - isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded') - }); const translateServiceStub: any = { getLangs: () => { @@ -38,145 +34,153 @@ fdescribe('LocaleService test suite', () => { return langList; }, // eslint-disable-next-line @typescript-eslint/no-empty-function - use: (param: string) => {} + use: (param: string) => { + } }; - const langList = ['en', 'it', 'de']; - - beforeEach(waitForAsync(() => { - return TestBed.configureTestingModule({ - imports: [ - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - providers: [ - { provide: CookieService, useValue: new CookieServiceMock() }, - { provide: AuthService, userValue: authService }, - { provide: RouteService, useValue: routeServiceStub }, - { provide: TranslateService, useValue: translateServiceStub }, - { provide: Document, useValue: document }, - ] - }); - })); - - beforeEach(() => { - cookieService = TestBed.inject(CookieService); - translateService = TestBed.inject(TranslateService); - routeService = TestBed.inject(RouteService); - window = new NativeWindowRef(); - document = { documentElement: { lang: 'en' } }; - service = new LocaleService(window, cookieService, translateService, authService, routeService, document); - serviceAsAny = service; - spyOnGet = spyOn(cookieService, 'get'); - spyOnSet = spyOn(cookieService, 'set'); - spyOnGetLanguage = spyOn(routeService, 'getQueryParameterValue').withArgs('lang'); + authService = jasmine.createSpyObj('AuthService', { + isAuthenticated: jasmine.createSpy('isAuthenticated'), + isAuthenticationLoaded: jasmine.createSpy('isAuthenticationLoaded') }); + const langList = ['en', 'xx', 'de']; + + describe('with valid language', () => { + + beforeEach(waitForAsync(() => { + return TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + providers: [ + { provide: CookieService, useValue: new CookieServiceMock() }, + { provide: AuthService, userValue: authService }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: TranslateService, useValue: translateServiceStub }, + { provide: Document, useValue: document }, + ] + }); + })); - describe('getCurrentLanguageCode', () => { - it('should return language saved on cookie', () => { - spyOnGet.and.returnValue('de'); - expect(service.getCurrentLanguageCode()).toBe('de'); + beforeEach(() => { + cookieService = TestBed.inject(CookieService); + translateService = TestBed.inject(TranslateService); + routeService = TestBed.inject(RouteService); + window = new NativeWindowRef(); + document = { documentElement: { lang: 'en' } }; + service = new LocaleService(window, cookieService, translateService, authService, routeService, document); + serviceAsAny = service; + spyOnGet = spyOn(cookieService, 'get'); + spyOnSet = spyOn(cookieService, 'set'); + spyOnGetLanguage = spyOn(routeService, 'getQueryParameterValue').withArgs('lang'); }); - describe('', () => { - beforeEach(() => { - spyOn(translateService, 'getLangs').and.returnValue(langList); + describe('getCurrentLanguageCode', () => { + it('should return language saved on cookie', () => { + spyOnGet.and.returnValue('de'); + expect(service.getCurrentLanguageCode()).toBe('de'); }); - it('should return language from browser setting', () => { - spyOn(translateService, 'getBrowserLang').and.returnValue('it'); - expect(service.getCurrentLanguageCode()).toBe('it'); - }); + describe('', () => { + beforeEach(() => { + spyOn(translateService, 'getLangs').and.returnValue(langList); + }); - it('should return default language from config', () => { - spyOn(translateService, 'getBrowserLang').and.returnValue('fr'); - expect(service.getCurrentLanguageCode()).toBe('en'); - }); - }); - }); + it('should return language from browser setting', () => { + spyOn(translateService, 'getBrowserLang').and.returnValue('xx'); + expect(service.getCurrentLanguageCode()).toBe('xx'); + }); - describe('getLanguageCodeFromCookie', () => { - it('should return language from cookie', () => { - spyOnGet.and.returnValue('de'); - expect(service.getLanguageCodeFromCookie()).toBe('de'); + it('should return default language from config', () => { + spyOn(translateService, 'getBrowserLang').and.returnValue('fr'); + expect(service.getCurrentLanguageCode()).toBe('en'); + }); + }); }); - }); + describe('getLanguageCodeFromCookie', () => { + it('should return language from cookie', () => { + spyOnGet.and.returnValue('de'); + expect(service.getLanguageCodeFromCookie()).toBe('de'); + }); - describe('saveLanguageCodeToCookie', () => { - it('should save language to cookie', () => { - service.saveLanguageCodeToCookie('en'); - expect(spyOnSet).toHaveBeenCalledWith(LANG_COOKIE, 'en'); }); - }); - describe('setCurrentLanguageCode', () => { - beforeEach(() => { - spyOn(service, 'saveLanguageCodeToCookie'); - spyOn(translateService, 'use'); + describe('saveLanguageCodeToCookie', () => { + it('should save language to cookie', () => { + service.saveLanguageCodeToCookie('en'); + expect(spyOnSet).toHaveBeenCalledWith(LANG_COOKIE, 'en'); + }); }); - it('should set the given language', () => { - service.setCurrentLanguageCode('it'); - expect(translateService.use).toHaveBeenCalledWith('it'); - expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('it'); - }); + describe('setCurrentLanguageCode', () => { + beforeEach(() => { + spyOn(service, 'saveLanguageCodeToCookie'); + spyOn(translateService, 'use'); + }); - it('should set the current language', () => { - spyOn(service, 'getCurrentLanguageCode').and.returnValue('es'); - service.setCurrentLanguageCode(); - expect(translateService.use).toHaveBeenCalledWith('es'); - expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es'); - }); + it('should set the given language', () => { + service.setCurrentLanguageCode('it'); + expect(translateService.use).toHaveBeenCalledWith('it'); + expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('it'); + }); - it('should set the current language on the html tag', () => { - spyOn(service, 'getCurrentLanguageCode').and.returnValue('es'); - service.setCurrentLanguageCode(); - expect((service as any).document.documentElement.lang).toEqual('es'); - }); + it('should set the current language', () => { + spyOn(service, 'getCurrentLanguageCode').and.returnValue('es'); + service.setCurrentLanguageCode(); + expect(translateService.use).toHaveBeenCalledWith('es'); + expect(service.saveLanguageCodeToCookie).toHaveBeenCalledWith('es'); + }); - describe('should set language on init', () => { - beforeEach(() => { - spyOn(translateService, 'getLangs').and.returnValue(langList); - spyOn(service, 'setCurrentLanguageCode'); + it('should set the current language on the html tag', () => { + spyOn(service, 'getCurrentLanguageCode').and.returnValue('es'); + service.setCurrentLanguageCode(); + expect((service as any).document.documentElement.lang).toEqual('es'); }); - describe('whith correct lang query param ', () => { + + describe('should set language on init', () => { beforeEach(() => { - spyOnGetLanguage.and.returnValue(observableOf('en')); - service.initDefaults(); - }); - it('should set correct lang', () => { - expect(service.setCurrentLanguageCode).toHaveBeenCalledWith('en'); + spyOn(translateService, 'getLangs').and.returnValue(langList); + spyOn(service, 'setCurrentLanguageCode'); }); - }); - describe('whith wrong lang query param ', () => { - beforeEach(() => { - spyOnGetLanguage.and.returnValue(observableOf('abcd')); - service.initDefaults(); + describe('whith correct lang query param ', () => { + beforeEach(() => { + spyOnGetLanguage.and.returnValue(observableOf('en')); + service.initDefaults(); + }); + it('should set correct lang', () => { + expect(service.setCurrentLanguageCode).toHaveBeenCalledWith('en'); + }); }); - it('should not set lang', () => { - expect(service.setCurrentLanguageCode).not.toHaveBeenCalled(); + describe('whith wrong lang query param ', () => { + beforeEach(() => { + spyOnGetLanguage.and.returnValue(observableOf('abcd')); + service.initDefaults(); + }); + it('should not set lang', () => { + expect(service.setCurrentLanguageCode).not.toHaveBeenCalled(); + }); }); }); }); - }); - describe('', () => { - it('should set quality to current language list', () => { - const langListWithQuality = ['en;q=1', 'it;q=0.9', 'de;q=0.8']; - spyOn(service, 'setQuality').and.returnValue(langListWithQuality); - service.setQuality(langList, LANG_ORIGIN.BROWSER, false); - expect(service.setQuality).toHaveBeenCalledWith(langList, LANG_ORIGIN.BROWSER, false); - }); + describe('', () => { + it('should set quality to current language list', () => { + const langListWithQuality = ['en;q=1', 'it;q=0.9', 'de;q=0.8']; + spyOn(service, 'setQuality').and.returnValue(langListWithQuality); + service.setQuality(langList, LANG_ORIGIN.BROWSER, false); + expect(service.setQuality).toHaveBeenCalledWith(langList, LANG_ORIGIN.BROWSER, false); + }); - it('should return the list of language with quality factor', () => { - spyOn(service, 'getLanguageCodeList'); - service.getLanguageCodeList(); - expect(service.getLanguageCodeList).toHaveBeenCalled(); + it('should return the list of language with quality factor', () => { + spyOn(service, 'getLanguageCodeList'); + service.getLanguageCodeList(); + expect(service.getLanguageCodeList).toHaveBeenCalled(); + }); }); }); }); From 32ead6af7d3ec1f25eb66bf883110b0c7fcad248 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 12 Dec 2023 15:51:47 +0100 Subject: [PATCH 27/33] [DSC-758] refactor language detection in locale service --- src/app/core/locale/locale.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/locale/locale.service.ts b/src/app/core/locale/locale.service.ts index 2cb94314256..f37803fa3c7 100644 --- a/src/app/core/locale/locale.service.ts +++ b/src/app/core/locale/locale.service.ts @@ -51,7 +51,7 @@ export class LocaleService { */ initDefaults() { this.routeService.getQueryParameterValue('lang').subscribe(lang => { - if (lang && this.translate.getLangs().some(language => language === lang)) { + if (lang && this.translate.getLangs().includes(lang)) { this.setCurrentLanguageCode(lang); } }); From 8f3f390859a1114dfc4983a4684a421f20487e7f Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 27 Mar 2024 15:03:53 +0100 Subject: [PATCH 28/33] [DSC-758] fix broken tests --- src/app/core/locale/locale.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/locale/locale.service.spec.ts b/src/app/core/locale/locale.service.spec.ts index 6195774a51d..2db176c5d16 100644 --- a/src/app/core/locale/locale.service.spec.ts +++ b/src/app/core/locale/locale.service.spec.ts @@ -12,7 +12,7 @@ import { RouteService } from '../services/route.service'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { of as observableOf } from 'rxjs'; -fdescribe('LocaleService test suite', () => { +describe('LocaleService test suite', () => { let service: LocaleService; let serviceAsAny: any; let cookieService: CookieService; From 27f38e651f9e4e5caa18eea99751f165ae6668ee Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 27 Mar 2024 15:37:44 +0100 Subject: [PATCH 29/33] [DSC-1437] fix broken tests --- .../search/lucky-search.component.spec.ts | 15 ++++----------- .../lucky-search/search/lucky-search.component.ts | 5 +++-- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/app/lucky-search/search/lucky-search.component.spec.ts b/src/app/lucky-search/search/lucky-search.component.spec.ts index 57de7472870..a25dfd93568 100644 --- a/src/app/lucky-search/search/lucky-search.component.spec.ts +++ b/src/app/lucky-search/search/lucky-search.component.spec.ts @@ -16,8 +16,6 @@ import { By } from '@angular/platform-browser'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service'; -import { RemoteData } from '../../core/data/remote-data'; -import { PaginatedList } from '../../core/data/paginated-list.model'; import { Bitstream } from '../../core/shared/bitstream.model'; import { RouterMock } from '../../shared/mocks/router.mock'; import { MetadataMap, MetadataValue } from '../../core/shared/metadata.models'; @@ -190,16 +188,11 @@ describe('SearchComponent', () => { const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult])); const metadataFilters = [{ metadataName: 'dc.title', metadataValue: 'test.pdf' }] as MetadataFilter[]; component.bitstreamFilters = metadataFilters; - bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {}).and.returnValue(observableOf({ - state: 'Success', - payload: { page: [bitstream] }, - get hasSucceeded(): boolean { - return true; - } - } as RemoteData>)); + bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {}) + .and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream]))); spyOn(component, 'redirect'); - spyOn(component.bitstreams$, 'next'); + spyOn(component.bitstreams$, 'next').and.callThrough(); spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree); component.resultsRD$.next(data as any); @@ -212,7 +205,7 @@ describe('SearchComponent', () => { }); it('should redirect to bitstream', () => { - expect(component.redirect).toHaveBeenCalledWith('/bitstreams/fa272dbf-e458-4ad2-868b-b4a27c6eac15/download'); + expect(component.redirect).toHaveBeenCalledWith(`/bitstreams/${bitstream.uuid}/download`); }); it('should return bitstream filename', () => { diff --git a/src/app/lucky-search/search/lucky-search.component.ts b/src/app/lucky-search/search/lucky-search.component.ts index 62d0e3a8e72..b7b1d588446 100644 --- a/src/app/lucky-search/search/lucky-search.component.ts +++ b/src/app/lucky-search/search/lucky-search.component.ts @@ -70,8 +70,9 @@ export class LuckySearchComponent implements OnInit, OnDestroy { this.searchOptions$ = this.getSearchOptions(); this.handleBitstreamResults(); this.readResult(); - const { queryParams } = this.router.parseUrl(this.router.url); - if (isNotEmpty(queryParams)) { + const urlTree = this.router.parseUrl(this.router.url); + if (isNotEmpty(urlTree?.queryParams)) { + const { queryParams } = urlTree; Object.keys(queryParams).forEach((key) => { if (key && key === 'index') { this.currentFilter.identifier = queryParams[key]; From 5473d76b8ab12f873159a07c68bf7d411df920c6 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 27 Mar 2024 17:24:00 +0100 Subject: [PATCH 30/33] [DSC-909] Restored default configuration --- src/config/default-app-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 31aa294396e..947100950e0 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -617,7 +617,7 @@ export class DefaultAppConfig implements AppConfig { layout: LayoutConfig = { navbar: { // If true, show the "Community and Collections" link in the navbar; otherwise, show it in the admin sidebar - showCommunityCollection: false, + showCommunityCollection: true, } }; From 7f2ca78cff31ecc6f50150435c628795fc84ab98 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Mar 2024 09:50:10 +0100 Subject: [PATCH 31/33] [DSC-1603] Fix issue on drag end --- src/app/shared/form/chips/chips.component.html | 2 +- src/app/shared/form/chips/chips.component.scss | 13 +------------ src/app/shared/form/chips/chips.component.ts | 1 + 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/app/shared/form/chips/chips.component.html b/src/app/shared/form/chips/chips.component.html index c6f47f37ec0..32cf0379232 100644 --- a/src/app/shared/form/chips/chips.component.html +++ b/src/app/shared/form/chips/chips.component.html @@ -20,7 +20,7 @@
diff --git a/src/app/shared/form/chips/chips.component.scss b/src/app/shared/form/chips/chips.component.scss index f809e41a353..d8dfe01b07a 100644 --- a/src/app/shared/form/chips/chips.component.scss +++ b/src/app/shared/form/chips/chips.component.scss @@ -1,14 +1,8 @@ -//.example-custom-placeholder { - //background: #ccc; - //border: none; - //min-height: 30px; - //border: 2px solid red; -//} - .cdk-drag-placeholder { filter: grayscale(100%); transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } + .cdk-drag-preview { color: white; box-sizing: border-box; @@ -17,11 +11,6 @@ 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); } -//.example-drag-preview.nav-item { -// margin-right: 2px; -// margin-bottom: 1px; -//} - .chip-stacked-icons { display: inline-block; position: relative; diff --git a/src/app/shared/form/chips/chips.component.ts b/src/app/shared/form/chips/chips.component.ts index 2d1cc75c30a..e893a90bf06 100644 --- a/src/app/shared/form/chips/chips.component.ts +++ b/src/app/shared/form/chips/chips.component.ts @@ -85,6 +85,7 @@ export class ChipsComponent implements OnChanges { onDrop(event: CdkDragDrop) { moveItemInArray(this.chips.chipsItems.getValue(), event.previousIndex, event.currentIndex); + this.dragged = -1; this.chips.updateOrder(); this.isDragging.next(false); } From f1e8c260c9a3bd78eecb47ab2ded7afdf77caa54 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Mar 2024 09:50:36 +0100 Subject: [PATCH 32/33] [DSC-1603] Fix margin for workflow actions --- .../workflow-item-search-result-list-element.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html index 2b03a38d50f..0d8c44b4ea2 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html @@ -5,7 +5,7 @@ [context]="badgeContext" [showThumbnails]="showThumbnails">
-
+
From ffffb6f23d89313f2bc26c9ad9a14f747ad51b0e Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 29 Mar 2024 14:29:17 +0100 Subject: [PATCH 33/33] [DSC-1437] Fixes wrong behavior in SSR --- .../search/lucky-search.component.html | 2 +- .../search/lucky-search.component.spec.ts | 5 +- .../search/lucky-search.component.ts | 47 ++++++++++--------- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/app/lucky-search/search/lucky-search.component.html b/src/app/lucky-search/search/lucky-search.component.html index d54c800df6f..76232635d29 100644 --- a/src/app/lucky-search/search/lucky-search.component.html +++ b/src/app/lucky-search/search/lucky-search.component.html @@ -30,7 +30,7 @@
{{'lucky.search.results.notfound' | translate}} {{curren
-
+
diff --git a/src/app/lucky-search/search/lucky-search.component.spec.ts b/src/app/lucky-search/search/lucky-search.component.spec.ts index a25dfd93568..93e0283efd5 100644 --- a/src/app/lucky-search/search/lucky-search.component.spec.ts +++ b/src/app/lucky-search/search/lucky-search.component.spec.ts @@ -19,6 +19,7 @@ import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream- import { Bitstream } from '../../core/shared/bitstream.model'; import { RouterMock } from '../../shared/mocks/router.mock'; import { MetadataMap, MetadataValue } from '../../core/shared/metadata.models'; +import { FileSizePipe } from '../../shared/utils/file-size-pipe'; describe('SearchComponent', () => { let fixture: ComponentFixture; @@ -67,7 +68,7 @@ describe('SearchComponent', () => { const routerStub = new RouterMock(); beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [LuckySearchComponent], + declarations: [LuckySearchComponent, FileSizePipe], imports: [TranslateModule.forRoot()], providers: [ {provide: Router, useValue: routerStub}, @@ -187,7 +188,7 @@ describe('SearchComponent', () => { }); const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult])); const metadataFilters = [{ metadataName: 'dc.title', metadataValue: 'test.pdf' }] as MetadataFilter[]; - component.bitstreamFilters = metadataFilters; + component.bitstreamFilters$.next(metadataFilters); bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {}) .and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream]))); diff --git a/src/app/lucky-search/search/lucky-search.component.ts b/src/app/lucky-search/search/lucky-search.component.ts index b7b1d588446..77849d606d5 100644 --- a/src/app/lucky-search/search/lucky-search.component.ts +++ b/src/app/lucky-search/search/lucky-search.component.ts @@ -9,7 +9,7 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../ import { SearchFilter } from '../../shared/search/models/search-filter.model'; import { LuckySearchService } from '../lucky-search.service'; import { Params, Router } from '@angular/router'; -import { filter, map, switchMap, tap } from 'rxjs/operators'; +import { filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import { Context } from '../../core/shared/context.model'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { Item } from '../../core/shared/item.model'; @@ -54,17 +54,18 @@ export class LuckySearchComponent implements OnInit, OnDestroy { private SOURCE_METADATA = 'dc.source'; private DESCRIPTION_METADATA = 'dc.description'; - bitstreamFilters: MetadataFilter[]; - bitstreams$ = new Subject(); + bitstreamFilters$ = new BehaviorSubject(null); + bitstreams$ = new BehaviorSubject(null); item$ = new Subject(); private readonly subscription = new Subscription(); - constructor(private luckySearchService: LuckySearchService, - private router: Router, - private bitstreamDataService: BitstreamDataService, - public searchConfigService: SearchConfigurationService) { - } + constructor( + private luckySearchService: LuckySearchService, + private router: Router, + private bitstreamDataService: BitstreamDataService, + public searchConfigService: SearchConfigurationService + ) {} ngOnInit(): void { this.searchOptions$ = this.getSearchOptions(); @@ -81,7 +82,8 @@ export class LuckySearchComponent implements OnInit, OnDestroy { this.currentFilter.value = queryParams[key]; } }); - this.bitstreamFilters = this.parseBitstreamFilters(queryParams); + const value = this.parseBitstreamFilters(queryParams); + this.bitstreamFilters$.next(value); } if (!(this.currentFilter.value !== '' && this.currentFilter.identifier !== '')) { this.showEmptySearchSection = true; @@ -101,11 +103,6 @@ export class LuckySearchComponent implements OnInit, OnDestroy { map(bitstreams => getBitstreamDownloadRoute(bitstreams[0])) ).subscribe(bitstreamRoute => this.redirect(bitstreamRoute)) ); - this.subscription.add( - this.bitstreams$.pipe( - filter(isEmpty) - ).subscribe(bitstreamRoute => this.showEmptySearchSection = true) - ); } private getLuckySearchResults(options: PaginatedSearchOptions) { @@ -141,17 +138,21 @@ export class LuckySearchComponent implements OnInit, OnDestroy { this.subscription.add( this.resultsRD$.pipe( filter(results => - this.isBitstreamSearch() && results?.payload?.totalElements === 1 + this.hasBitstreamFilters() && results?.payload?.totalElements === 1 ), map(results => results.payload.page[0].indexableObject as Item), tap(item => this.item$.next(item)), - mergeMap(item => this.loadBitstreamsAndRedirectIfNeeded(item)) - ).subscribe(results => this.bitstreams$.next(results)) + withLatestFrom(this.bitstreamFilters$), + mergeMap(([item, bitstreamFilters]) => this.loadBitstreamsAndRedirectIfNeeded(item, bitstreamFilters)), + ).subscribe(results => { + this.showEmptySearchSection = isEmpty(results); + this.bitstreams$.next(results); + }) ); this.subscription.add( this.resultsRD$.pipe( filter(results => - !this.isBitstreamSearch() && results?.payload?.totalElements === 1 + !this.hasBitstreamFilters() && results?.payload?.totalElements === 1 ), map(results => results.payload.page[0].indexableObject as Item), tap(item => this.item$.next(item)), @@ -165,8 +166,8 @@ export class LuckySearchComponent implements OnInit, OnDestroy { ); } - private isBitstreamSearch() { - return this.bitstreamFilters?.length; + private hasBitstreamFilters(): boolean { + return this.bitstreamFilters$.getValue()?.length > 0; } private getSearchOptions(): Observable { @@ -193,11 +194,11 @@ export class LuckySearchComponent implements OnInit, OnDestroy { return []; } - private loadBitstreamsAndRedirectIfNeeded(item: Item): Observable { - return this.bitstreamDataService.findByItem(item.uuid, 'ORIGINAL', this.bitstreamFilters, {}) + private loadBitstreamsAndRedirectIfNeeded(item: Item, bitstreamFilters: MetadataFilter[]): Observable { + return this.bitstreamDataService.findByItem(item.uuid, 'ORIGINAL', bitstreamFilters, {}) .pipe( getFirstCompletedRemoteData(), - map(bitstreamsResult => bitstreamsResult.payload?.page) + map(bitstreamsResult => bitstreamsResult.payload?.page), ); }