From b43d19f1bab79054200c6f187ca121eb7a940831 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Wed, 13 Mar 2024 17:49:48 +0100 Subject: [PATCH 01/40] [DSC-1482] Replaced method 'findAllByItemAndBundleName' --- .../file-section/full-file-section.component.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.ts index 6b66b48a8c6..919c0395cd0 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.ts @@ -67,9 +67,10 @@ export class FullFileSectionComponent extends FileSectionComponent implements On initialize(): void { this.originals$ = this.paginationService.getCurrentPagination(this.originalOptions.id, this.originalOptions).pipe( - switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( - this.item, + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.showableByItem( + this.item.uuid, 'ORIGINAL', + [], {elementsPerPage: options.pageSize, currentPage: options.currentPage}, true, true, @@ -85,9 +86,10 @@ export class FullFileSectionComponent extends FileSectionComponent implements On ); this.licenses$ = this.paginationService.getCurrentPagination(this.licenseOptions.id, this.licenseOptions).pipe( - switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( - this.item, + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.showableByItem( + this.item.uuid, 'LICENSE', + [], {elementsPerPage: options.pageSize, currentPage: options.currentPage}, true, true, From bc2ffeb636a79093d80eb71119d0c85f56ff32ce Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Thu, 14 Mar 2024 18:22:37 +0100 Subject: [PATCH 02/40] [DSC-1482] Modified spec.ts according new implementation --- .../file-section/full-file-section.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts index b2e956efafb..8eb0c06e74d 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -22,6 +22,7 @@ import { APP_CONFIG } from 'src/config/app-config.interface'; import { environment } from 'src/environments/environment'; import { UUIDService } from '../../../../core/shared/uuid.service'; import { getMockUUIDService } from '../../../../shared/mocks/uuid.service.mock'; +import {Item} from '../../../../core/shared/item.model'; describe('FullFileSectionComponent', () => { let comp: FullFileSectionComponent; @@ -55,7 +56,7 @@ describe('FullFileSectionComponent', () => { }); const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { - findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])) + showableByItem: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])), }); const paginationService = new PaginationServiceStub(); @@ -85,6 +86,7 @@ describe('FullFileSectionComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(FullFileSectionComponent); comp = fixture.componentInstance; + comp.item = new Item(); fixture.detectChanges(); })); From d54b2b9f86c50448ae66b9e039f8b7c0185a5ae3 Mon Sep 17 00:00:00 2001 From: Sondissimo Date: Fri, 29 Sep 2023 14:56:42 +0200 Subject: [PATCH 03/40] [DSC-1468] port of GLAM-353 entity type dropdown is now organized in alphabetical order --- .../collection-form.component.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/app/collection-page/collection-form/collection-form.component.ts b/src/app/collection-page/collection-form/collection-form.component.ts index 4519e911db7..7343585bc91 100644 --- a/src/app/collection-page/collection-form/collection-form.component.ts +++ b/src/app/collection-page/collection-form/collection-form.component.ts @@ -135,20 +135,23 @@ export class CollectionFormComponent extends ComColFormComponent imp // retrieve all entity types and submission definitions to populate the dropdowns selection combineLatest([entities$, definitions$]) - .subscribe(([entityTypes, definitions]: [ItemType[], SubmissionDefinitionModel[]]) => { - - entityTypes = entityTypes.filter((type: ItemType) => type.label !== NONE_ENTITY_TYPE); - entityTypes.forEach((type: ItemType, index: number) => { - this.entityTypeSelection.add({ - disabled: false, - label: type.label, - value: type.label - } as DynamicFormOptionConfig); - if (currentRelationshipValue && currentRelationshipValue.length > 0 && currentRelationshipValue[0].value === type.label) { - this.entityTypeSelection.select(index); - this.entityTypeSelection.disabled = true; - } - }); + .subscribe(([entityTypes, definitions]: [ItemType[], SubmissionDefinitionModel[]]) => { + + const sortedEntityTypes = entityTypes + .filter((type: ItemType) => type.label !== NONE_ENTITY_TYPE) + .sort((a, b) => a.label.localeCompare(b.label)); + + sortedEntityTypes.forEach((type: ItemType, index: number) => { + this.entityTypeSelection.add({ + disabled: false, + label: type.label, + value: type.label + } as DynamicFormOptionConfig); + if (currentRelationshipValue && currentRelationshipValue.length > 0 && currentRelationshipValue[0].value === type.label) { + this.entityTypeSelection.select(index); + this.entityTypeSelection.disabled = true; + } + }); definitions.forEach((definition: SubmissionDefinitionModel, index: number) => { this.submissionDefinitionSelection.add({ From 9a93fb246e4c8729997b8dcfd57c39353e4a8533 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Mon, 25 Mar 2024 16:02:05 +0100 Subject: [PATCH 04/40] [DSC-1594] Implementing hide field inside edit bitstream view --- .../edit-bitstream-page.component.spec.ts | 20 ++++++- .../edit-bitstream-page.component.ts | 56 ++++++++++++++++++- src/assets/i18n/en.json5 | 4 ++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index 522da6ac84b..d73a8a0d7c6 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -25,6 +25,10 @@ import { Item } from '../../core/shared/item.model'; import { MetadataValueFilter } from '../../core/shared/metadata.models'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; +import {VocabularyService} from '../../core/submission/vocabularies/vocabulary.service'; +import {VocabularyEntry} from '../../core/submission/vocabularies/models/vocabulary-entry.model'; +import {buildPaginatedList} from '../../core/data/paginated-list.model'; +import {PageInfo} from '../../core/shared/page-info.model'; const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); @@ -49,11 +53,22 @@ let fixture: ComponentFixture; describe('EditBitstreamPageComponent', () => { + const entries = [ + Object.assign(new VocabularyEntry(), {display: 'true', value: 'true' }), + Object.assign(new VocabularyEntry(), {display: 'false', value: 'false' }), + ]; + + const mockVocabularyService = jasmine.createSpyObj('vocabularyService', { + getVocabularyEntries: jasmine.createSpy('getVocabularyEntries'), + }); + beforeEach(() => { bitstreamID = 'current-bitstream-id'; currentPrimary = bitstreamID; differentPrimary = '12345-abcde-54321-edcba'; + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); + allFormats = [ Object.assign({ id: '1', @@ -213,6 +228,7 @@ describe('EditBitstreamPageComponent', () => { { provide: DSONameService, useValue: dsoNameService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, + { provide: VocabularyService, useValue: mockVocabularyService}, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -511,6 +527,7 @@ describe('EditBitstreamPageComponent', () => { {provide: DSONameService, useValue: dsoNameService}, {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, + { provide: VocabularyService, useValue: mockVocabularyService}, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -549,7 +566,7 @@ describe('EditBitstreamPageComponent', () => { }); }); - describe('ignore OTHERCONTENT bundle', () => { + describe('ignore OTHERCONTENT bundle', () => { const bundleName = 'OTHERCONTENT'; @@ -640,6 +657,7 @@ describe('EditBitstreamPageComponent', () => { {provide: DSONameService, useValue: dsoNameService}, {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, + { provide: VocabularyService, useValue: mockVocabularyService}, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index 831e6547065..7827f184ffd 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -26,6 +26,10 @@ import { Item } from '../../core/shared/item.model'; import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model'; import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; +import {VocabularyService} from '../../core/submission/vocabularies/vocabulary.service'; +import {VocabularyOptions} from '../../core/submission/vocabularies/models/vocabulary-options.model'; +import {PageInfo} from '../../core/shared/page-info.model'; +import {VocabularyEntry} from '../../core/submission/vocabularies/models/vocabulary-entry.model'; @Component({ selector: 'ds-edit-bitstream-page', @@ -254,6 +258,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } }); + hideModel: DynamicSelectModel; + /** * All input models in a simple array for easier iterations */ @@ -326,6 +332,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { host: 'col-12 d-inline-block' } }, + hide: { + grid: { + host: 'col-12 d-inline-block' + } + }, embargo: { grid: { host: 'col-12 d-inline-block' @@ -405,6 +416,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { private notificationsService: NotificationsService, private bitstreamFormatService: BitstreamFormatDataService, private primaryBitstreamService: PrimaryBitstreamService, + private vocabularyService: VocabularyService ) { } @@ -446,15 +458,24 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { switchMap((bundle: Bundle) => bundle.item), getFirstSucceededRemoteDataPayload(), ); + const vocabularyOptions = new VocabularyOptions( + 'truefalse', + 'bitstream.hide', + ); + const pageInfo = new PageInfo(); + const hide$: Observable = this.vocabularyService.getVocabularyEntries(vocabularyOptions, pageInfo).pipe( + getFirstCompletedRemoteData(), + map((rq)=> rq.hasSucceeded ? rq.payload.page : []), + ); this.subs.push( - observableCombineLatest( + observableCombineLatest([ bitstream$, allFormats$, bundle$, primaryBitstream$, item$, - ).pipe() - .subscribe(([bitstream, allFormats, bundle, primaryBitstream, item]) => { + hide$, + ]).subscribe(([bitstream, allFormats, bundle, primaryBitstream, item,entries]) => { this.bitstream = bitstream as Bitstream; this.formats = allFormats.page; this.bundle = bundle; @@ -462,6 +483,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { // be a success response, but empty this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null; this.itemId = item.uuid; + this.handleHideBitstream(entries); this.setIiifStatus(this.bitstream); }) ); @@ -473,6 +495,26 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }) ); } + handleHideBitstream(entries: VocabularyEntry[]) { + if (isEmpty(entries)) { + return; + } + const options = entries.map((entry) => ({label: entry.display, value: entry.value})); + let hideModel = new DynamicSelectModel({ + id: 'hide', + name: 'hide', + options: options + }); + this.hideModel = hideModel; + this.inputModels.push(this.hideModel); + const groupModel = new DynamicFormGroupModel({ + id: 'hideContainer', + group: [ + hideModel + ] + }); + this.formModel.push(groupModel); + } /** * Initializes the form. @@ -500,6 +542,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { fileTypeContainer: { fileType: bitstream.firstMetadataValue('dc.type') }, + hideContainer: { + hide: bitstream.firstMetadataValue('bitstream.hide') + }, formatContainer: { newFormat: hasValue(bitstream.firstMetadata('dc.format')) ? bitstream.firstMetadata('dc.format').value : undefined } @@ -711,6 +756,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } else { Metadata.setFirstValue(newMetadata, 'dc.type', rawForm.fileTypeContainer.fileType); } + if (isEmpty(rawForm.hideContainer.hide)) { + delete newMetadata['bitstream.hide']; + } else { + Metadata.setFirstValue(newMetadata, 'bitstream.hide', rawForm.hideContainer.hide); + } if (this.isIIIF) { // It's helpful to remove these metadata elements entirely when the form value is empty. // This avoids potential issues on the REST side and makes it possible to do things like diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e1fa6450c7c..d3ed75f9ed4 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -896,6 +896,10 @@ "bitstream.edit.form.fileType.hint": "Personal picture, logo, main article, etc.", + "bitstream.edit.form.hide.label": "Hide", + + "bitstream.edit.form.hide.hint": "Hide the bitstream on the item page.", + "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", "bitstream.edit.form.selectedFormat.label": "Selected Format", From 6d23762a1b5ada268d87b31e690851cb0e52398e Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Wed, 27 Mar 2024 11:22:30 +0100 Subject: [PATCH 05/40] [DSC-1594] Updated the spec file related --- .../edit-bitstream-page.component.spec.ts | 269 +++++++++++++++++- 1 file changed, 266 insertions(+), 3 deletions(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index d73a8a0d7c6..94258796ae9 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -51,7 +51,7 @@ let bundle; let comp: EditBitstreamPageComponent; let fixture: ComponentFixture; -describe('EditBitstreamPageComponent', () => { +fdescribe('EditBitstreamPageComponent', () => { const entries = [ Object.assign(new VocabularyEntry(), {display: 'true', value: 'true' }), @@ -67,8 +67,6 @@ describe('EditBitstreamPageComponent', () => { currentPrimary = bitstreamID; differentPrimary = '12345-abcde-54321-edcba'; - mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); - allFormats = [ Object.assign({ id: '1', @@ -155,6 +153,7 @@ describe('EditBitstreamPageComponent', () => { describe('EditBitstreamPageComponent no IIIF fields', () => { beforeEach(waitForAsync(() => { + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); bundle = { _links: { primaryBitstream: { @@ -440,6 +439,7 @@ describe('EditBitstreamPageComponent', () => { const bundleName = 'ORIGINAL'; beforeEach(waitForAsync(() => { + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); bitstream = Object.assign(new Bitstream(), { metadata: { @@ -571,6 +571,7 @@ describe('EditBitstreamPageComponent', () => { const bundleName = 'OTHERCONTENT'; beforeEach(waitForAsync(() => { + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); bitstream = Object.assign(new Bitstream(), { metadata: { @@ -688,4 +689,266 @@ describe('EditBitstreamPageComponent', () => { }); }); + describe('EditBitstreamPageComponent with metadata hide', () => { + + beforeEach(waitForAsync(() => { + bundle = { + _links: { + primaryBitstream: { + href: 'bundle-selflink' + } + }, + item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { + uuid: 'some-uuid', + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return undefined; + }, + })) + }; + const bundleName = 'ORIGINAL'; + + bitstream = Object.assign(new Bitstream(), { + uuid: bitstreamID, + id: bitstreamID, + metadata: { + 'dc.description': [ + { + value: 'Bitstream description' + } + ], + 'dc.title': [ + { + value: 'Bitstream title' + } + ], + 'dc.type': [ + { + value: 'Logo' + } + ], + 'bitstream.hide': [ + { + value: 'false' + } + ] + }, + format: createSuccessfulRemoteDataObject$(selectedFormat), + _links: { + self: 'bitstream-selflink' + }, + bundle: createSuccessfulRemoteDataObject$(bundle) + }); + bitstreamService = jasmine.createSpyObj('bitstreamService', { + findById: createSuccessfulRemoteDataObject$(bitstream), + findByHref: createSuccessfulRemoteDataObject$(bitstream), + update: createSuccessfulRemoteDataObject$(bitstream), + updateFormat: createSuccessfulRemoteDataObject$(bitstream), + commitUpdates: {}, + patch: {} + }); + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) + }); + dsoNameService = jasmine.createSpyObj('dsoNameService', { + getName: bundleName + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], + providers: [ + { provide: NotificationsService, useValue: notificationsService }, + { provide: DynamicFormService, useValue: formService }, + { + provide: ActivatedRoute, + useValue: { + data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), + snapshot: { queryParams: {} } + } + }, + { provide: BitstreamDataService, useValue: bitstreamService }, + { provide: DSONameService, useValue: dsoNameService }, + { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, + { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, + { provide: VocabularyService, useValue: mockVocabularyService}, + ChangeDetectorRef + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + describe('when there are vocabulary entries', () =>{ + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + }); + let rawForm; + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); + it('should have a select with value false', ()=>{ + expect(rawForm.hideContainer.hide).toEqual(bitstream.firstMetadataValue('bitstream.hide')); + }); + it('should verify that hide model has the correct model with options arriving from entries', ()=>{ + expect(comp.hideModel).toBeDefined(); + expect(comp.hideModel.id).toBe('hide'); + expect(comp.hideModel.name).toBe('hide'); + expect(comp.hideModel.options.length).toBe(2); + expect(comp.hideModel.options[0].label).toBe('true'); + expect(comp.hideModel.options[0].value).toBe('true'); + expect(comp.hideModel.options[1].label).toBe('false'); + expect(comp.hideModel.options[1].value).toBe('false'); + }); + }); + describe('when there no vocabulary entries', () =>{ + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))); + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + }); + let rawForm; + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); + it('should verify that form model has the correct model with options arriving from entries', ()=>{ + expect(comp.hideModel).toBeUndefined(); + expect(rawForm.hideContainer).toBeUndefined(); + }); + }); + }); + + describe('EditBitstreamPageComponent without metadata hide', () => { + + beforeEach(waitForAsync(() => { + bundle = { + _links: { + primaryBitstream: { + href: 'bundle-selflink' + } + }, + item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { + uuid: 'some-uuid', + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return undefined; + }, + })) + }; + const bundleName = 'ORIGINAL'; + + bitstream = Object.assign(new Bitstream(), { + uuid: bitstreamID, + id: bitstreamID, + metadata: { + 'dc.description': [ + { + value: 'Bitstream description' + } + ], + 'dc.title': [ + { + value: 'Bitstream title' + } + ], + 'dc.type': [ + { + value: 'Logo' + } + ] + }, + format: createSuccessfulRemoteDataObject$(selectedFormat), + _links: { + self: 'bitstream-selflink' + }, + bundle: createSuccessfulRemoteDataObject$(bundle) + }); + bitstreamService = jasmine.createSpyObj('bitstreamService', { + findById: createSuccessfulRemoteDataObject$(bitstream), + findByHref: createSuccessfulRemoteDataObject$(bitstream), + update: createSuccessfulRemoteDataObject$(bitstream), + updateFormat: createSuccessfulRemoteDataObject$(bitstream), + commitUpdates: {}, + patch: {} + }); + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) + }); + dsoNameService = jasmine.createSpyObj('dsoNameService', { + getName: bundleName + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], + providers: [ + { provide: NotificationsService, useValue: notificationsService }, + { provide: DynamicFormService, useValue: formService }, + { + provide: ActivatedRoute, + useValue: { + data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), + snapshot: { queryParams: {} } + } + }, + { provide: BitstreamDataService, useValue: bitstreamService }, + { provide: DSONameService, useValue: dsoNameService }, + { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, + { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, + { provide: VocabularyService, useValue: mockVocabularyService}, + ChangeDetectorRef + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + describe('when there are no vocabulary entries', () =>{ + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))); + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + }); + let rawForm; + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); + it('should have a select with 0 elements', ()=>{ + expect(rawForm.hideContainer).toBeUndefined(); + expect(comp.hideModel).toBeUndefined(); + }); + }); + describe('when there are vocabulary entries', () =>{ + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + mockVocabularyService.getVocabularyEntries.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), entries))); + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + }); + let rawForm; + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); + it('should have a select with 2 elements', ()=>{ + expect(rawForm.hideContainer).toBeDefined(); + expect(comp.hideModel).toBeDefined(); + expect(comp.hideModel.id).toBe('hide'); + expect(comp.hideModel.name).toBe('hide'); + expect(comp.hideModel.options.length).toBe(2); + expect(comp.hideModel.options[0].label).toBe('true'); + expect(comp.hideModel.options[0].value).toBe('true'); + expect(comp.hideModel.options[1].label).toBe('false'); + expect(comp.hideModel.options[1].value).toBe('false'); + }); + }); + }); }); From 79240be818553fc917a5516b85470cebf2159062 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Wed, 27 Mar 2024 11:23:25 +0100 Subject: [PATCH 06/40] [DSC-1594] Removed the fdescribe --- .../edit-bitstream-page/edit-bitstream-page.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index 94258796ae9..3c9487d3050 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -51,7 +51,7 @@ let bundle; let comp: EditBitstreamPageComponent; let fixture: ComponentFixture; -fdescribe('EditBitstreamPageComponent', () => { +describe('EditBitstreamPageComponent', () => { const entries = [ Object.assign(new VocabularyEntry(), {display: 'true', value: 'true' }), From d0a670e1fcfc96081a5ed6c58aebd7676863df63 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 2 Apr 2024 16:42:44 +0200 Subject: [PATCH 07/40] [DSC-1570] add basic structure for datadog rum settings --- package.json | 1 + src/app/app.component.ts | 9 +++++++++ src/config/app-config.interface.ts | 2 ++ src/config/datadog-rum-config.interfaces.ts | 16 ++++++++++++++++ src/config/default-app-config.ts | 15 +++++++++++++++ yarn.lock | 20 ++++++++++++++++++++ 6 files changed, 63 insertions(+) create mode 100644 src/config/datadog-rum-config.interfaces.ts diff --git a/package.json b/package.json index 8bfdfabec7a..747e3c0e1c7 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@angular/platform-server": "^15.2.8", "@angular/router": "^15.2.8", "@babel/runtime": "7.21.0", + "@datadog/browser-rum": "^5.7.0", "@kolkov/ngx-gallery": "^2.0.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.11.3", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 24889066b5b..70cb80e07b7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -29,6 +29,7 @@ import { distinctNext } from './core/shared/distinct-next'; import { RouteService } from './core/services/route.service'; import { getEditItemPageRoute, getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from './app-routing-paths'; import { SocialService } from './social/social.service'; +import { datadogRum } from '@datadog/browser-rum'; @Component({ selector: 'ds-app', @@ -108,6 +109,14 @@ export class AppComponent implements OnInit, AfterViewInit { ); this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); + + if ( + environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + // TODO: aggiungere check consent cookie + console.warn('init', environment.datadogRum); + datadogRum.init(environment.datadogRum); + } } private storeCSSVariables() { diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 3b41ea234fd..5ea5fa2c9d5 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -32,6 +32,7 @@ import { AdvancedAttachmentRenderingConfig } from './advanced-attachment-renderi import { AttachmentRenderingConfig } from './attachment-rendering.config'; import { SearchResultConfig } from './search-result-config.interface'; import { MiradorConfig } from './mirador-config.interfaces'; +import { DatadogRumConfig } from './datadog-rum-config.interfaces'; interface AppConfig extends Config { ui: UIServerConfig; @@ -70,6 +71,7 @@ interface AppConfig extends Config { advancedAttachmentRendering: AdvancedAttachmentRenderingConfig; searchResult: SearchResultConfig; mirador: MiradorConfig; + datadogRum?: DatadogRumConfig; } /** diff --git a/src/config/datadog-rum-config.interfaces.ts b/src/config/datadog-rum-config.interfaces.ts new file mode 100644 index 00000000000..d8a632d28c2 --- /dev/null +++ b/src/config/datadog-rum-config.interfaces.ts @@ -0,0 +1,16 @@ +import { Config } from './config.interface'; +import { DefaultPrivacyLevel } from '@datadog/browser-rum'; + +export interface DatadogRumConfig extends Config { + clientToken: string; + applicationId: string; + site?: string; + service: string; + env: string; + sessionSampleRate?: number; + sessionReplaySampleRate?: number; + trackUserInteractions?: boolean; + trackResources?: boolean; + trackLongTasks?: boolean; + defaultPrivacyLevel?: DefaultPrivacyLevel; +} diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 947100950e0..6e470a340b4 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -35,6 +35,7 @@ import { import { AttachmentRenderingConfig } from './attachment-rendering.config'; import { SearchResultConfig } from './search-result-config.interface'; import { MiradorConfig } from './mirador-config.interfaces'; +import { DatadogRumConfig } from './datadog-rum-config.interfaces'; export class DefaultAppConfig implements AppConfig { production = false; @@ -760,4 +761,18 @@ export class DefaultAppConfig implements AppConfig { mirador: MiradorConfig = { enableDownloadPlugin: true, }; + + datadogRum: DatadogRumConfig = { + clientToken: undefined, + applicationId: undefined, + site: 'datadoghq.eu', + service: undefined, + env: undefined, + sessionSampleRate: 50, + sessionReplaySampleRate: 20, + trackUserInteractions: true, + trackResources: true, + trackLongTasks: true, + defaultPrivacyLevel: 'mask-user-input', + }; } diff --git a/yarn.lock b/yarn.lock index 27edf4ec5fc..5d7e803c2aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1533,6 +1533,26 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@datadog/browser-core@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@datadog/browser-core/-/browser-core-5.7.0.tgz#ae8cd791626e928a82dfdfb3ccea88778323ddb8" + integrity sha512-rNUe3s5XD+9UUe/muYuh/UJzb0YXTRwSP1VevMRZmEpNgkBhurWjqjm3LQphYXMYsmQIgvqDPWm05Z+jOxTzRw== + +"@datadog/browser-rum-core@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@datadog/browser-rum-core/-/browser-rum-core-5.7.0.tgz#a13f88ab094fe95fe807292965e031857f06b3e3" + integrity sha512-ZWDkyMprM0QBQhKhEZnfux8EE9STPa1wl/rAjY/+dBRqrGi1bEqigSgy7GiwiCag1Wn9GEjLXS5opqmoO/fpQw== + dependencies: + "@datadog/browser-core" "5.7.0" + +"@datadog/browser-rum@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@datadog/browser-rum/-/browser-rum-5.7.0.tgz#5aa157f0f175f8a35675d9c542437b6f7646a667" + integrity sha512-I+1QslFl4vteoJQBAtBYPgiWa1rFZ/DoNZsOqsKGJjpql0omv5fvdjwbSTBBT5wfLnzo/FnfHogynFpB+JP0rg== + dependencies: + "@datadog/browser-core" "5.7.0" + "@datadog/browser-rum-core" "5.7.0" + "@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" From d67f4219f50483c8a04e06a8af7bee9b5072a1f7 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Wed, 3 Apr 2024 15:05:49 +0200 Subject: [PATCH 08/40] [DSC-1570] add klaro cookie management --- src/app/app.component.ts | 16 +++++++++------- src/app/shared/cookies/browser-klaro.service.ts | 11 +++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 70cb80e07b7..e423527d34c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -30,6 +30,7 @@ import { RouteService } from './core/services/route.service'; import { getEditItemPageRoute, getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from './app-routing-paths'; import { SocialService } from './social/social.service'; import { datadogRum } from '@datadog/browser-rum'; +import { KlaroService } from './shared/cookies/klaro.service'; @Component({ selector: 'ds-app', @@ -75,6 +76,7 @@ export class AppComponent implements OnInit, AfterViewInit { private modalService: NgbModal, private modalConfig: NgbModalConfig, private socialService: SocialService, + private klaroService: KlaroService ) { this.notificationOptions = environment.notifications; @@ -110,13 +112,13 @@ export class AppComponent implements OnInit, AfterViewInit { this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); - if ( - environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && - environment.datadogRum?.service && environment.datadogRum?.env) { - // TODO: aggiungere check consent cookie - console.warn('init', environment.datadogRum); - datadogRum.init(environment.datadogRum); - } + this.klaroService.getSavedPreferences().subscribe(savedPreferences => { + if (savedPreferences?.datadog && + environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + datadogRum.init(environment.datadogRum); + } + }); } private storeCSSVariables() { diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 0db3d5464ef..29b9f78229c 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -117,6 +117,17 @@ export class BrowserKlaroService extends KlaroService { }); } + if (environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + this.klaroConfig.services.push( + { + name: 'datadog', + purposes: ['thirdPartyJs'], + required: false, + } + ); + } + const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( getFirstCompletedRemoteData(), map(remoteData => !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)), From 8e5bf8b502e4a4fab3eb29df987ff7607ee444ff Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Wed, 3 Apr 2024 17:21:35 +0200 Subject: [PATCH 09/40] [DSC-1570] create DatadogRumService --- src/app/app.component.ts | 13 ++----- .../datadog-rum/datadog-rum.service.spec.ts | 16 ++++++++ .../shared/datadog-rum/datadog-rum.service.ts | 38 +++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 src/app/shared/datadog-rum/datadog-rum.service.spec.ts create mode 100644 src/app/shared/datadog-rum/datadog-rum.service.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e423527d34c..2b6ad96f315 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -29,8 +29,7 @@ import { distinctNext } from './core/shared/distinct-next'; import { RouteService } from './core/services/route.service'; import { getEditItemPageRoute, getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from './app-routing-paths'; import { SocialService } from './social/social.service'; -import { datadogRum } from '@datadog/browser-rum'; -import { KlaroService } from './shared/cookies/klaro.service'; +import { DatadogRumService } from './shared/datadog-rum/datadog-rum.service'; @Component({ selector: 'ds-app', @@ -76,7 +75,7 @@ export class AppComponent implements OnInit, AfterViewInit { private modalService: NgbModal, private modalConfig: NgbModalConfig, private socialService: SocialService, - private klaroService: KlaroService + private datadogRumService: DatadogRumService ) { this.notificationOptions = environment.notifications; @@ -112,13 +111,7 @@ export class AppComponent implements OnInit, AfterViewInit { this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); - this.klaroService.getSavedPreferences().subscribe(savedPreferences => { - if (savedPreferences?.datadog && - environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && - environment.datadogRum?.service && environment.datadogRum?.env) { - datadogRum.init(environment.datadogRum); - } - }); + this.datadogRumService.initDatadogRum(); } private storeCSSVariables() { diff --git a/src/app/shared/datadog-rum/datadog-rum.service.spec.ts b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts new file mode 100644 index 00000000000..5b9a0234df6 --- /dev/null +++ b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DatadogRumService } from './datadog-rum.service'; + +describe('DatadogRumService', () => { + let service: DatadogRumService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DatadogRumService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/datadog-rum/datadog-rum.service.ts b/src/app/shared/datadog-rum/datadog-rum.service.ts new file mode 100644 index 00000000000..bb2b23a4194 --- /dev/null +++ b/src/app/shared/datadog-rum/datadog-rum.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { datadogRum } from '@datadog/browser-rum'; +import { CookieConsents, KlaroService } from '../cookies/klaro.service'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class DatadogRumService { + + consentUpdates$: BehaviorSubject; + isDatadogInitialized = false; + + constructor( + private klaroService: KlaroService + ) { + } + + initDatadogRum() { + this.klaroService.watchConsentUpdates(); + this.consentUpdates$ = this.klaroService.consentsUpdates$; + this.consentUpdates$.subscribe(savedPreferences => { + if (savedPreferences?.datadog && + environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + if (!this.isDatadogInitialized) { + this.isDatadogInitialized = true; + datadogRum.init(environment.datadogRum); + } + } else { + // TODO: if a session starts then stops then starts again an error is thrown. Is there an alternative to the .init method? + datadogRum.stopSession(); + this.isDatadogInitialized = false; + } + }); + } +} From 16ce735f43a685abd0572445a80c50b9d2280f34 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Thu, 4 Apr 2024 08:01:33 +0200 Subject: [PATCH 10/40] [DSC-1570] fix wrong usage of datadogRum library --- src/app/shared/datadog-rum/datadog-rum.service.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/shared/datadog-rum/datadog-rum.service.ts b/src/app/shared/datadog-rum/datadog-rum.service.ts index bb2b23a4194..cc99e69edcd 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.ts @@ -11,6 +11,7 @@ export class DatadogRumService { consentUpdates$: BehaviorSubject; isDatadogInitialized = false; + isDatadogRunning = false; constructor( private klaroService: KlaroService @@ -26,12 +27,15 @@ export class DatadogRumService { environment.datadogRum?.service && environment.datadogRum?.env) { if (!this.isDatadogInitialized) { this.isDatadogInitialized = true; + this.isDatadogRunning = true; datadogRum.init(environment.datadogRum); + } else if (!this.isDatadogRunning) { + this.isDatadogRunning = true; + datadogRum.startSessionReplayRecording(); } } else { - // TODO: if a session starts then stops then starts again an error is thrown. Is there an alternative to the .init method? - datadogRum.stopSession(); - this.isDatadogInitialized = false; + datadogRum.stopSessionReplayRecording(); + this.isDatadogRunning = false; } }); } From 13b3d183d7939eb4923f6c6613154cfbbd74cc68 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Thu, 4 Apr 2024 09:44:30 +0200 Subject: [PATCH 11/40] [DSC-1570] set up store for datadog rum --- src/app/core/core-state.model.ts | 2 + src/app/core/core.reducers.ts | 4 +- .../shared/datadog-rum/datadog-rum.actions.ts | 20 ++++++ .../shared/datadog-rum/datadog-rum.reducer.ts | 29 +++++++++ .../shared/datadog-rum/datadog-rum.service.ts | 62 +++++++++++++------ 5 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 src/app/shared/datadog-rum/datadog-rum.actions.ts create mode 100644 src/app/shared/datadog-rum/datadog-rum.reducer.ts diff --git a/src/app/core/core-state.model.ts b/src/app/core/core-state.model.ts index b8211fdb555..eb7422b5a11 100644 --- a/src/app/core/core-state.model.ts +++ b/src/app/core/core-state.model.ts @@ -11,6 +11,7 @@ import { JsonPatchOperationsState } from './json-patch/json-patch-operations.red import { MetaTagState } from './metadata/meta-tag.reducer'; import { RouteState } from './services/route.reducer'; import { RequestState } from './data/request-state.model'; +import { DatadogRumState } from '../shared/datadog-rum/datadog-rum.reducer'; /** * The core sub-state in the NgRx store @@ -27,4 +28,5 @@ export interface CoreState { 'json/patch': JsonPatchOperationsState; 'metaTag': MetaTagState; 'route': RouteState; + 'datadogRum': DatadogRumState; } diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index c0165c53848..9a588d3a9de 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -14,6 +14,7 @@ import { import { historyReducer } from './history/history.reducer'; import { metaTagReducer } from './metadata/meta-tag.reducer'; import { CoreState } from './core-state.model'; +import { datadogRumReducer } from '../shared/datadog-rum/datadog-rum.reducer'; export const coreReducers: ActionReducerMap = { 'bitstreamFormats': bitstreamFormatReducer, @@ -26,5 +27,6 @@ export const coreReducers: ActionReducerMap = { 'auth': authReducer, 'json/patch': jsonPatchOperationsReducer, 'metaTag': metaTagReducer, - 'route': routeReducer + 'route': routeReducer, + 'datadogRum': datadogRumReducer, }; diff --git a/src/app/shared/datadog-rum/datadog-rum.actions.ts b/src/app/shared/datadog-rum/datadog-rum.actions.ts new file mode 100644 index 00000000000..2972ae29ea7 --- /dev/null +++ b/src/app/shared/datadog-rum/datadog-rum.actions.ts @@ -0,0 +1,20 @@ +import { Action } from '@ngrx/store'; +import { type } from '../ngrx/type'; + +export const DatadogRumActionTypes = { + SET_STATUS: type('dspace/datadog-rum/SET_IS_INITIALIZED'), +}; + +export class setDatadogRumStatusAction implements Action { + type = DatadogRumActionTypes.SET_STATUS; + payload: { + isInitialized?: boolean; + isRunning?: boolean; + }; + + constructor(status: { isInitialized?: boolean, isRunning?: boolean }) { + this.payload = status; + } +} + +export type DatadogRumAction = setDatadogRumStatusAction; diff --git a/src/app/shared/datadog-rum/datadog-rum.reducer.ts b/src/app/shared/datadog-rum/datadog-rum.reducer.ts new file mode 100644 index 00000000000..c49cef66ba9 --- /dev/null +++ b/src/app/shared/datadog-rum/datadog-rum.reducer.ts @@ -0,0 +1,29 @@ +import { DatadogRumAction, DatadogRumActionTypes } from './datadog-rum.actions'; + +export interface DatadogRumState { + isInitialized: boolean; + isRunning: boolean; +} + +const initialState: DatadogRumState = Object.create({isInitialized: false, isRunning: false}); + +export function datadogRumReducer(state = initialState, action: DatadogRumAction): DatadogRumState { + + switch (action.type) { + + case DatadogRumActionTypes.SET_STATUS: { + return setDatadogRumStatus(state, action); + } + + default: { + return state; + } + } +} + +function setDatadogRumStatus(state: DatadogRumState, action: DatadogRumAction) { + return { + ...state, + ...action.payload + }; +} diff --git a/src/app/shared/datadog-rum/datadog-rum.service.ts b/src/app/shared/datadog-rum/datadog-rum.service.ts index cc99e69edcd..8d6c2abff16 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.ts @@ -2,7 +2,15 @@ import { Injectable } from '@angular/core'; import { environment } from '../../../environments/environment'; import { datadogRum } from '@datadog/browser-rum'; import { CookieConsents, KlaroService } from '../cookies/klaro.service'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { createSelector, Store } from '@ngrx/store'; +import { setDatadogRumStatusAction } from './datadog-rum.actions'; +import { DatadogRumState } from './datadog-rum.reducer'; +import { distinctUntilChanged, take } from 'rxjs/operators'; +import { coreSelector } from '../../core/core.selectors'; +import { CoreState } from '../../core/core-state.model'; + +export const getDatadogRumState = createSelector(coreSelector, (state: CoreState) => state.datadogRum); @Injectable({ providedIn: 'root' @@ -10,11 +18,10 @@ import { BehaviorSubject } from 'rxjs'; export class DatadogRumService { consentUpdates$: BehaviorSubject; - isDatadogInitialized = false; - isDatadogRunning = false; constructor( - private klaroService: KlaroService + private klaroService: KlaroService, + private store: Store ) { } @@ -22,21 +29,40 @@ export class DatadogRumService { this.klaroService.watchConsentUpdates(); this.consentUpdates$ = this.klaroService.consentsUpdates$; this.consentUpdates$.subscribe(savedPreferences => { - if (savedPreferences?.datadog && - environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && - environment.datadogRum?.service && environment.datadogRum?.env) { - if (!this.isDatadogInitialized) { - this.isDatadogInitialized = true; - this.isDatadogRunning = true; - datadogRum.init(environment.datadogRum); - } else if (!this.isDatadogRunning) { - this.isDatadogRunning = true; - datadogRum.startSessionReplayRecording(); + this.getDatadogRumState().subscribe((state) => { + if (savedPreferences?.datadog && + environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + if (!state.isInitialized) { + this.store.dispatch(new setDatadogRumStatusAction({ + isInitialized: true, + isRunning: true + })); + datadogRum.init(environment.datadogRum); + } else if (!state.isRunning) { + this.store.dispatch(new setDatadogRumStatusAction({ + isRunning: true + })); + datadogRum.startSessionReplayRecording(); + } + } else { + datadogRum.stopSessionReplayRecording(); + this.store.dispatch(new setDatadogRumStatusAction({ + isRunning: false + })); } - } else { - datadogRum.stopSessionReplayRecording(); - this.isDatadogRunning = false; - } + }); }); } + + + getDatadogRumState(): Observable { + return this.store + .select(getDatadogRumState) + .pipe( + distinctUntilChanged(), + take(1), + ); + } } + From aa796fe45bd7616385352c6f5837a2a7bbd83640 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Thu, 4 Apr 2024 14:53:47 +0200 Subject: [PATCH 12/40] [DSC-1570] write tests --- .../datadog-rum/datadog-rum.service.spec.ts | 81 ++++++++++++++++++- .../shared/datadog-rum/datadog-rum.service.ts | 11 ++- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/app/shared/datadog-rum/datadog-rum.service.spec.ts b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts index 5b9a0234df6..548e13e9290 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.spec.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts @@ -1,16 +1,93 @@ import { TestBed } from '@angular/core/testing'; - +import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { DatadogRumService } from './datadog-rum.service'; +import { CookieConsents, KlaroService } from '../cookies/klaro.service'; +import { of } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { setDatadogRumStatusAction } from './datadog-rum.actions'; describe('DatadogRumService', () => { let service: DatadogRumService; + let store: MockStore; + let klaroService: KlaroService; + let memoizedSelector; + + const initialState = { + datadogRum: { + isInitialized: false, + isRunning: false + } + }; + + const consentsAccepted: CookieConsents = { + datadog: true + }; + + const klaroServiceSpy = jasmine.createSpyObj('KlaroService', { + getSavedPreferences: jasmine.createSpy('getSavedPreferences'), + watchConsentUpdates: jasmine.createSpy('watchConsentUpdates') + }, { + consentsUpdates$: of(consentsAccepted) + }); + + const datadogRumEnvironmentOptions = { + clientToken: 'clientToken', + applicationId: 'applicationId', + service: 'service', + env: 'env' + }; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + DatadogRumService, + provideMockStore({initialState}), + {provide: KlaroService, useValue: klaroServiceSpy}, + ] + }); service = TestBed.inject(DatadogRumService); + store = TestBed.inject(MockStore); + memoizedSelector = store.overrideSelector(service.datadogRumStateSelector, initialState.datadogRum); + klaroService = TestBed.inject(KlaroService); + + spyOn(store, 'dispatch'); }); it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should dispatch setDatadogRumStatusAction with isInitialized and isRunning true when datadog cookie is accepted', () => { + memoizedSelector.setResult({isInitialized: false, isRunning: false}); + store.refreshState(); + consentsAccepted.datadog = true; + environment.datadogRum = datadogRumEnvironmentOptions; + service.initDatadogRum(); + expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({ + isInitialized: true, + isRunning: true + })); + }); + + it('should dispatch setDatadogRumStatusAction with isRunning true when datadog cookie is accepted and isInitialized is true', () => { + memoizedSelector.setResult({isInitialized: true, isRunning: false}); + store.refreshState(); + consentsAccepted.datadog = true; + environment.datadogRum = datadogRumEnvironmentOptions; + service.initDatadogRum(); + expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({ + isRunning: true + })); + }); + + it('should dispatch setDatadogRumStatusAction with isRunning false when datadog cookie is not accepted', () => { + memoizedSelector.setResult({isInitialized: true, isRunning: true}); + store.refreshState(); + consentsAccepted.datadog = false; + service.initDatadogRum(); + expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({ + isRunning: false + })); + }); + }); diff --git a/src/app/shared/datadog-rum/datadog-rum.service.ts b/src/app/shared/datadog-rum/datadog-rum.service.ts index 8d6c2abff16..4ffce1ee73f 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.ts @@ -10,14 +10,13 @@ import { distinctUntilChanged, take } from 'rxjs/operators'; import { coreSelector } from '../../core/core.selectors'; import { CoreState } from '../../core/core-state.model'; -export const getDatadogRumState = createSelector(coreSelector, (state: CoreState) => state.datadogRum); - @Injectable({ providedIn: 'root' }) export class DatadogRumService { - consentUpdates$: BehaviorSubject; + consentsUpdates$: BehaviorSubject; + datadogRumStateSelector = createSelector(coreSelector, (state: CoreState) => state.datadogRum); constructor( private klaroService: KlaroService, @@ -27,8 +26,8 @@ export class DatadogRumService { initDatadogRum() { this.klaroService.watchConsentUpdates(); - this.consentUpdates$ = this.klaroService.consentsUpdates$; - this.consentUpdates$.subscribe(savedPreferences => { + this.consentsUpdates$ = this.klaroService.consentsUpdates$; + this.consentsUpdates$.subscribe(savedPreferences => { this.getDatadogRumState().subscribe((state) => { if (savedPreferences?.datadog && environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && @@ -58,7 +57,7 @@ export class DatadogRumService { getDatadogRumState(): Observable { return this.store - .select(getDatadogRumState) + .select(this.datadogRumStateSelector) .pipe( distinctUntilChanged(), take(1), From cccaf7004bbdc94fa6b4719df3d38a9ab5eb6286 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Thu, 4 Apr 2024 15:09:56 +0200 Subject: [PATCH 13/40] [DSC-1570] add i18n --- src/assets/i18n/en.json5 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e278948f734..d358002d9be 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1819,6 +1819,10 @@ "cookies.consent.app.description.dimensions": "Dimensions analyses references from publications and calculates a set of article-level indicators. (https://www.dimensions.ai)", + "cookies.consent.app.title.datadog": "Datadog", + + "cookies.consent.app.description.datadog": "Datadog provides insights on how users interact with the application.", + "cookies.consent.purpose.thirdPartyJs": "Third-party JavaScript", "curation-task.task.citationpage.label": "Generate Citation Page", From 7e5d0fd054386c717bb4b6d534d7504bf2f5c8b9 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Thu, 4 Apr 2024 16:11:11 +0200 Subject: [PATCH 14/40] [DSC-1570] fix tests --- src/app/app.component.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 10d3d04192d..e3bec87c4f4 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -37,6 +37,7 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { of } from 'rxjs'; import { APP_CONFIG } from '../config/app-config.interface'; import { environment } from '../environments/environment'; +import { KlaroService } from './shared/cookies/klaro.service'; let comp: AppComponent; let fixture: ComponentFixture; @@ -55,6 +56,7 @@ describe('App component', () => { let breadcrumbsServiceSpy; let routeServiceMock; + let klaroServiceSpy: jasmine.SpyObj; const getDefaultTestBedConf = () => { breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']); @@ -62,6 +64,13 @@ describe('App component', () => { getCurrentUrl: of('/home') }); + klaroServiceSpy = jasmine.createSpyObj('KlaroService', { + getSavedPreferences: jasmine.createSpy('getSavedPreferences'), + watchConsentUpdates: jasmine.createSpy('watchConsentUpdates') + },{ + consentsUpdates$: of({}) + }); + return { imports: [ CommonModule, @@ -89,6 +98,7 @@ describe('App component', () => { { provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy }, { provide: RouteService, useValue: routeServiceMock }, { provide: APP_CONFIG, useValue: environment }, + { provide: KlaroService, useValue: klaroServiceSpy }, provideMockStore({ initialState }), AppComponent, // RouteService From c1fd4911d23ab91acb0620718e0a64153171487f Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 29 Apr 2024 13:11:52 +0200 Subject: [PATCH 15/40] [DSC-1665] add automatic retries to loading component --- src/app/shared/loading/loading.component.ts | 40 ++++++++++++++++--- .../loading/themed-loading.component.ts | 2 + src/config/default-app-config.ts | 1 + src/config/loader-config.interfaces.ts | 1 + src/environments/environment.test.ts | 1 + 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/app/shared/loading/loading.component.ts b/src/app/shared/loading/loading.component.ts index f21a41c4f55..bac4be31386 100644 --- a/src/app/shared/loading/loading.component.ts +++ b/src/app/shared/loading/loading.component.ts @@ -1,10 +1,11 @@ -import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { hasValue } from '../empty.util'; import { environment } from '../../../environments/environment'; import { AlertType } from '../alert/alert-type'; +import { NativeWindowRef, NativeWindowService } from '../../core/services/window.service'; enum MessageType { LOADING = 'loading', @@ -19,6 +20,8 @@ enum MessageType { }) export class LoadingComponent implements OnDestroy, OnInit { + readonly QUERY_PARAM_RELOAD_COUNT = 'reloadCount'; + @Input() message: string; @Input() showMessage = true; @@ -28,6 +31,8 @@ export class LoadingComponent implements OnDestroy, OnInit { @Input() errorMessage: string; @Input() errorMessageDelay = environment.loader.errorMessageDelay; + @Input() numberOfAutomaticPageReloads = environment.loader.numberOfAutomaticPageReloads || 0; + /** * Show a more compact spinner animation instead of the default one */ @@ -39,13 +44,29 @@ export class LoadingComponent implements OnDestroy, OnInit { warningTimeout: any; errorTimeout: any; + pageReloadCount = 0; + readonly AlertTypeEnum = AlertType; - constructor(private translate: TranslateService, private changeDetectorRef: ChangeDetectorRef) { + constructor( + @Inject(NativeWindowService) private _window: NativeWindowRef, + private translate: TranslateService, + private changeDetectorRef: ChangeDetectorRef) { } ngOnInit() { + // get current page reload count from query parameters + const queryParams = new URLSearchParams(this._window.nativeWindow.location.search); + const reloadCount = queryParams.get(this.QUERY_PARAM_RELOAD_COUNT); + if (hasValue(reloadCount)) { + this.pageReloadCount = +reloadCount; + // clear reload count from query parameters + queryParams.delete(this.QUERY_PARAM_RELOAD_COUNT); + this._window.nativeWindow.history.replaceState({}, '', + `${this._window.nativeWindow.location.pathname}${queryParams.keys.length ? '?' + queryParams.toString() : ''}`); + } + if (this.showMessage) { this.message = this.message || this.translate.instant('loading.default'); } @@ -59,10 +80,19 @@ export class LoadingComponent implements OnDestroy, OnInit { }, this.warningMessageDelay); } if (this.errorMessageDelay > 0) { + const errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); this.errorTimeout = setTimeout(() => { - this.messageToShow = MessageType.ERROR; - this.changeDetectorRef.detectChanges(); - }, this.errorMessageDelay); + if (this.pageReloadCount < this.numberOfAutomaticPageReloads) { + this.pageReloadCount++; + // add reload count to query parameters, then reload the page + queryParams.set(this.QUERY_PARAM_RELOAD_COUNT, this.pageReloadCount.toString()); + this._window.nativeWindow.history.replaceState({}, '', `${this._window.nativeWindow.location.pathname}?${queryParams}`); + this._window.nativeWindow.location.reload(); + } else { + this.messageToShow = MessageType.ERROR; + this.changeDetectorRef.detectChanges(); + } + }, errorTimeoutWithRetriesDelay); } } } diff --git a/src/app/shared/loading/themed-loading.component.ts b/src/app/shared/loading/themed-loading.component.ts index 501d807fc6c..4f8444cb13a 100644 --- a/src/app/shared/loading/themed-loading.component.ts +++ b/src/app/shared/loading/themed-loading.component.ts @@ -17,6 +17,7 @@ export class ThemedLoadingComponent extends ThemedComponent { @Input() showMessage: boolean; @Input() spinner: boolean; @Input() showFallbackMessages: boolean; + @Input() numberOfAutomaticPageReloads: number; @Input() warningMessage: string; @Input() warningMessageDelay: number; @Input() errorMessage: string; @@ -27,6 +28,7 @@ export class ThemedLoadingComponent extends ThemedComponent { 'showMessage', 'spinner', 'showFallbackMessages', + 'numberOfAutomaticPageReloads', 'warningMessage', 'warningMessageDelay', 'errorMessage', diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 8f7646171ea..d15d450e32c 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -766,5 +766,6 @@ export class DefaultAppConfig implements AppConfig { showFallbackMessagesByDefault: false, warningMessageDelay: 5000, // 5 seconds errorMessageDelay: 15000, // 15 seconds + numberOfAutomaticPageReloads: 2, }; } diff --git a/src/config/loader-config.interfaces.ts b/src/config/loader-config.interfaces.ts index f9523959f98..af0e5fc1270 100644 --- a/src/config/loader-config.interfaces.ts +++ b/src/config/loader-config.interfaces.ts @@ -4,4 +4,5 @@ export interface LoaderConfig extends Config { showFallbackMessagesByDefault: boolean; warningMessageDelay: number; errorMessageDelay: number; + numberOfAutomaticPageReloads: number; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 2346a3f55e7..268646adeb8 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -582,5 +582,6 @@ export const environment: BuildConfig = { showFallbackMessagesByDefault: true, warningMessageDelay: 1000, errorMessageDelay: 2000, + numberOfAutomaticPageReloads: 2, }, }; From 83f4225f1b2f2003131458a3d4277b04bcf1e403 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 29 Apr 2024 15:38:18 +0200 Subject: [PATCH 16/40] [DSC-1665] add tests --- .../shared/loading/loading.component.spec.ts | 30 ++++++++++++++++++- src/app/shared/loading/loading.component.ts | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/app/shared/loading/loading.component.spec.ts b/src/app/shared/loading/loading.component.spec.ts index 9f358a6158b..40d03b7f29b 100644 --- a/src/app/shared/loading/loading.component.spec.ts +++ b/src/app/shared/loading/loading.component.spec.ts @@ -7,6 +7,7 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; import { LoadingComponent } from './loading.component'; +import { NativeWindowService } from '../../core/services/window.service'; describe('LoadingComponent (inline template)', () => { @@ -15,6 +16,19 @@ describe('LoadingComponent (inline template)', () => { let de: DebugElement; let el: HTMLElement; + let windowSpy = jasmine.createSpyObj('NativeWindowService', [],{ + nativeWindow: { + location: { + search: '', + pathname: '', + reload: () => null + }, + history: { + replaceState: () => null + } + } + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -26,8 +40,12 @@ describe('LoadingComponent (inline template)', () => { }), ], declarations: [LoadingComponent], // declare the test component - providers: [TranslateService] + providers: [ + TranslateService, + { provide: NativeWindowService, useValue: windowSpy }, + ] }).compileComponents(); // compile template and css + })); beforeEach(() => { @@ -75,4 +93,14 @@ describe('LoadingComponent (inline template)', () => { expect(de).toBeTruthy(); }); + it('should add time if the page has been automatically reloaded', () => { + comp.pageReloadCount = 1; + comp.errorMessageDelay = 1000; + comp.warningMessageDelay = 500; + comp.numberOfAutomaticPageReloads = 2; + comp.ngOnInit(); + + expect(comp.errorTimeoutWithRetriesDelay).toBe(1500); + }); + }); diff --git a/src/app/shared/loading/loading.component.ts b/src/app/shared/loading/loading.component.ts index bac4be31386..9ec8b592229 100644 --- a/src/app/shared/loading/loading.component.ts +++ b/src/app/shared/loading/loading.component.ts @@ -30,6 +30,8 @@ export class LoadingComponent implements OnDestroy, OnInit { @Input() warningMessageDelay = environment.loader.warningMessageDelay; @Input() errorMessage: string; @Input() errorMessageDelay = environment.loader.errorMessageDelay; + errorTimeoutWithRetriesDelay = environment.loader.errorMessageDelay; + @Input() numberOfAutomaticPageReloads = environment.loader.numberOfAutomaticPageReloads || 0; @@ -66,6 +68,7 @@ export class LoadingComponent implements OnDestroy, OnInit { this._window.nativeWindow.history.replaceState({}, '', `${this._window.nativeWindow.location.pathname}${queryParams.keys.length ? '?' + queryParams.toString() : ''}`); } + this.errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); if (this.showMessage) { this.message = this.message || this.translate.instant('loading.default'); @@ -80,7 +83,6 @@ export class LoadingComponent implements OnDestroy, OnInit { }, this.warningMessageDelay); } if (this.errorMessageDelay > 0) { - const errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); this.errorTimeout = setTimeout(() => { if (this.pageReloadCount < this.numberOfAutomaticPageReloads) { this.pageReloadCount++; @@ -92,7 +94,7 @@ export class LoadingComponent implements OnDestroy, OnInit { this.messageToShow = MessageType.ERROR; this.changeDetectorRef.detectChanges(); } - }, errorTimeoutWithRetriesDelay); + }, this.errorTimeoutWithRetriesDelay); } } } From 8646f5d5a84e6088bdfdd6b99e93be71269cbe54 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 30 Apr 2024 10:09:22 +0200 Subject: [PATCH 17/40] [DSC-1665] switch to router instead of window.location.reload still need to remove the queryparam from the url after first reload --- src/app/shared/loading/loading.component.ts | 23 +++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/app/shared/loading/loading.component.ts b/src/app/shared/loading/loading.component.ts index 9ec8b592229..a67af59e1e4 100644 --- a/src/app/shared/loading/loading.component.ts +++ b/src/app/shared/loading/loading.component.ts @@ -1,11 +1,11 @@ -import { ChangeDetectorRef, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { hasValue } from '../empty.util'; import { environment } from '../../../environments/environment'; import { AlertType } from '../alert/alert-type'; -import { NativeWindowRef, NativeWindowService } from '../../core/services/window.service'; +import { Router } from '@angular/router'; enum MessageType { LOADING = 'loading', @@ -51,7 +51,7 @@ export class LoadingComponent implements OnDestroy, OnInit { readonly AlertTypeEnum = AlertType; constructor( - @Inject(NativeWindowService) private _window: NativeWindowRef, + private router: Router, private translate: TranslateService, private changeDetectorRef: ChangeDetectorRef) { @@ -59,14 +59,14 @@ export class LoadingComponent implements OnDestroy, OnInit { ngOnInit() { // get current page reload count from query parameters - const queryParams = new URLSearchParams(this._window.nativeWindow.location.search); + const queryParams = new URLSearchParams(this.router.url.split('?')[1]); const reloadCount = queryParams.get(this.QUERY_PARAM_RELOAD_COUNT); + let currentUrl = this.router.url.split('?')[0]; if (hasValue(reloadCount)) { this.pageReloadCount = +reloadCount; // clear reload count from query parameters queryParams.delete(this.QUERY_PARAM_RELOAD_COUNT); - this._window.nativeWindow.history.replaceState({}, '', - `${this._window.nativeWindow.location.pathname}${queryParams.keys.length ? '?' + queryParams.toString() : ''}`); + this.router.navigate([currentUrl], {queryParams: queryParams, skipLocationChange: true}); } this.errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); @@ -87,9 +87,14 @@ export class LoadingComponent implements OnDestroy, OnInit { if (this.pageReloadCount < this.numberOfAutomaticPageReloads) { this.pageReloadCount++; // add reload count to query parameters, then reload the page - queryParams.set(this.QUERY_PARAM_RELOAD_COUNT, this.pageReloadCount.toString()); - this._window.nativeWindow.history.replaceState({}, '', `${this._window.nativeWindow.location.pathname}?${queryParams}`); - this._window.nativeWindow.location.reload(); + const queryParamsObj = {}; + queryParamsObj[this.QUERY_PARAM_RELOAD_COUNT] = this.pageReloadCount; + queryParams.forEach((value, key) => { + queryParamsObj[key] = value; + }); + this.router.navigateByUrl('/fake-url', { skipLocationChange: true }).then(() => { + this.router.navigate([currentUrl], { queryParams: queryParamsObj, queryParamsHandling: 'merge', onSameUrlNavigation: 'reload'}); + }); } else { this.messageToShow = MessageType.ERROR; this.changeDetectorRef.detectChanges(); From 0014b2d89ec18f8272af5cb9bfceea7853e765eb Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 30 Apr 2024 12:05:40 +0200 Subject: [PATCH 18/40] [DSC-1665] complete switch to router --- src/app/shared/loading/loading.component.ts | 25 +++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/app/shared/loading/loading.component.ts b/src/app/shared/loading/loading.component.ts index a67af59e1e4..d3fb3054c60 100644 --- a/src/app/shared/loading/loading.component.ts +++ b/src/app/shared/loading/loading.component.ts @@ -6,6 +6,7 @@ import { hasValue } from '../empty.util'; import { environment } from '../../../environments/environment'; import { AlertType } from '../alert/alert-type'; import { Router } from '@angular/router'; +import { Location } from '@angular/common'; enum MessageType { LOADING = 'loading', @@ -52,21 +53,18 @@ export class LoadingComponent implements OnDestroy, OnInit { constructor( private router: Router, + private location: Location, private translate: TranslateService, private changeDetectorRef: ChangeDetectorRef) { } ngOnInit() { - // get current page reload count from query parameters - const queryParams = new URLSearchParams(this.router.url.split('?')[1]); - const reloadCount = queryParams.get(this.QUERY_PARAM_RELOAD_COUNT); let currentUrl = this.router.url.split('?')[0]; + const queryParams = new URLSearchParams(this.router.url.split('?')[1]); + const reloadCount = (this.location.getState() as any)?.[this.QUERY_PARAM_RELOAD_COUNT]; if (hasValue(reloadCount)) { this.pageReloadCount = +reloadCount; - // clear reload count from query parameters - queryParams.delete(this.QUERY_PARAM_RELOAD_COUNT); - this.router.navigate([currentUrl], {queryParams: queryParams, skipLocationChange: true}); } this.errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); @@ -86,14 +84,13 @@ export class LoadingComponent implements OnDestroy, OnInit { this.errorTimeout = setTimeout(() => { if (this.pageReloadCount < this.numberOfAutomaticPageReloads) { this.pageReloadCount++; - // add reload count to query parameters, then reload the page - const queryParamsObj = {}; - queryParamsObj[this.QUERY_PARAM_RELOAD_COUNT] = this.pageReloadCount; - queryParams.forEach((value, key) => { - queryParamsObj[key] = value; - }); - this.router.navigateByUrl('/fake-url', { skipLocationChange: true }).then(() => { - this.router.navigate([currentUrl], { queryParams: queryParamsObj, queryParamsHandling: 'merge', onSameUrlNavigation: 'reload'}); + this.router.navigate(['/fake-url'], {queryParams, queryParamsHandling: 'merge', skipLocationChange: true}).then(() => { + this.router.navigate([currentUrl], { + queryParams, + queryParamsHandling: 'merge', + onSameUrlNavigation: 'reload', + state: {[this.QUERY_PARAM_RELOAD_COUNT]: this.pageReloadCount} + }); }); } else { this.messageToShow = MessageType.ERROR; From c20d3a4af9dc93a7eb55809e58739fbb860e69ae Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 30 Apr 2024 12:23:01 +0200 Subject: [PATCH 19/40] [DSC-1665] change tests --- .../shared/loading/loading.component.spec.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/app/shared/loading/loading.component.spec.ts b/src/app/shared/loading/loading.component.spec.ts index 40d03b7f29b..0f8be81786d 100644 --- a/src/app/shared/loading/loading.component.spec.ts +++ b/src/app/shared/loading/loading.component.spec.ts @@ -7,7 +7,8 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; import { LoadingComponent } from './loading.component'; -import { NativeWindowService } from '../../core/services/window.service'; +import { Router } from '@angular/router'; +import { RouterMock } from '../mocks/router.mock'; describe('LoadingComponent (inline template)', () => { @@ -16,19 +17,6 @@ describe('LoadingComponent (inline template)', () => { let de: DebugElement; let el: HTMLElement; - let windowSpy = jasmine.createSpyObj('NativeWindowService', [],{ - nativeWindow: { - location: { - search: '', - pathname: '', - reload: () => null - }, - history: { - replaceState: () => null - } - } - }); - beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -42,7 +30,7 @@ describe('LoadingComponent (inline template)', () => { declarations: [LoadingComponent], // declare the test component providers: [ TranslateService, - { provide: NativeWindowService, useValue: windowSpy }, + {provide: Router, useValue: new RouterMock()}, ] }).compileComponents(); // compile template and css From 2589c7556be9992b327b60f081040ef1710d0610 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 30 Apr 2024 12:56:14 +0200 Subject: [PATCH 20/40] [DSC-1665] add comments --- src/app/shared/loading/loading.component.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app/shared/loading/loading.component.ts b/src/app/shared/loading/loading.component.ts index d3fb3054c60..06b5823d9b0 100644 --- a/src/app/shared/loading/loading.component.ts +++ b/src/app/shared/loading/loading.component.ts @@ -60,31 +60,44 @@ export class LoadingComponent implements OnDestroy, OnInit { } ngOnInit() { + // saving current url and query params for the upcoming router trick let currentUrl = this.router.url.split('?')[0]; const queryParams = new URLSearchParams(this.router.url.split('?')[1]); + + // get reload count from state const reloadCount = (this.location.getState() as any)?.[this.QUERY_PARAM_RELOAD_COUNT]; if (hasValue(reloadCount)) { this.pageReloadCount = +reloadCount; } + + // calculate the delay for the error message with retries this.errorTimeoutWithRetriesDelay = this.errorMessageDelay + this.pageReloadCount * (this.errorMessageDelay - this.warningMessageDelay); if (this.showMessage) { this.message = this.message || this.translate.instant('loading.default'); } + if (this.showFallbackMessages) { this.warningMessage = this.warningMessage || this.translate.instant('loading.warning'); this.errorMessage = this.errorMessage || this.translate.instant('loading.error'); + if (this.warningMessageDelay > 0) { this.warningTimeout = setTimeout(() => { this.messageToShow = MessageType.WARNING; this.changeDetectorRef.detectChanges(); }, this.warningMessageDelay); } + if (this.errorMessageDelay > 0) { this.errorTimeout = setTimeout(() => { + // if the page has been reloaded less than the maximum number of retries if (this.pageReloadCount < this.numberOfAutomaticPageReloads) { this.pageReloadCount++; + // navigate to a fake url to trigger a reload of the current page + // this is needed because the router does not reload the page if the url is the same, + // even if the state changes and the onSameUrlNavigation property is set to 'reload' this.router.navigate(['/fake-url'], {queryParams, queryParamsHandling: 'merge', skipLocationChange: true}).then(() => { + // navigate back to the current url this.router.navigate([currentUrl], { queryParams, queryParamsHandling: 'merge', @@ -93,6 +106,7 @@ export class LoadingComponent implements OnDestroy, OnInit { }); }); } else { + // if the page has been reloaded the maximum number of retries this.messageToShow = MessageType.ERROR; this.changeDetectorRef.detectChanges(); } From c341b38d0eb7cadce9133b33e448d8c1cbe3dd80 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 30 Apr 2024 16:13:46 +0200 Subject: [PATCH 21/40] [DSC-1668] fix when page changes on search-chart-filter --- .../search-chart-filter.component.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/shared/search/search-charts/search-chart/search-chart-filter/search-chart-filter.component.ts b/src/app/shared/search/search-charts/search-chart/search-chart-filter/search-chart-filter.component.ts index ec1cf154e38..bc7a3e70619 100644 --- a/src/app/shared/search/search-charts/search-chart/search-chart-filter/search-chart-filter.component.ts +++ b/src/app/shared/search/search-charts/search-chart/search-chart-filter/search-chart-filter.component.ts @@ -90,6 +90,18 @@ export class SearchChartFilterComponent extends SearchFacetFilterComponent imple } queryParam[str[0]] = queryParam[str[0]] ? [...queryParam[str[0]], str[1]] : [str[1]]; }); + + if (this.currentUrl) { + const currentQueryParams = this.currentUrl.split('?')[1].split('&'); + const pageParam = currentQueryParams.filter((param) => param.includes('page')); + if (pageParam.length > 0) { + const paramName = pageParam[0].split('=')[0]; + queryParam[paramName] = [1]; + } + } else { + queryParam['spc.page'] = [1]; + } + this.router.navigate(this.getSearchLinkParts(), { queryParams: queryParam, queryParamsHandling: 'merge', From d1401a684a878211e4acb4f6b2a4b83ee0b747ae Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 2 May 2024 10:03:07 +0200 Subject: [PATCH 22/40] [DSC-1597] fixed position of vertical collapsible sidebar button --- .../cris-layout-sidebar/cris-layout-sidebar.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/cris-layout/cris-layout-loader/cris-layout-vertical/cris-layout-sidebar/cris-layout-sidebar.component.html b/src/app/cris-layout/cris-layout-loader/cris-layout-vertical/cris-layout-sidebar/cris-layout-sidebar.component.html index 5820514cd51..567e39165db 100644 --- a/src/app/cris-layout/cris-layout-loader/cris-layout-vertical/cris-layout-sidebar/cris-layout-sidebar.component.html +++ b/src/app/cris-layout/cris-layout-loader/cris-layout-vertical/cris-layout-sidebar/cris-layout-sidebar.component.html @@ -20,10 +20,10 @@ -
+
- \ No newline at end of file + From c51ad5affa93e8a47a979ce9d341a1670d13d4e9 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 17 May 2024 12:51:05 +0000 Subject: [PATCH 23/40] Merged in task/sangallen/CST-14956 (pull request #1715) [CST-14956] fix of scrollable-dropdown menu on retrieving entries Approved-by: Francesco Molinaro --- .../dynamic-scrollable-dropdown.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts index 404cabf4be9..62192641439 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts @@ -157,7 +157,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom this.pageInfo.totalElements, this.pageInfo.totalPages ); - this.retrieveEntries(this.searchText, false, true); + this.retrieveEntries(this.searchText, false, true, true); } } @@ -203,13 +203,13 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom * @param concatResults If true concat results to the current list * @private */ - private retrieveEntries(searchText = null, initModel = false, concatResults = false) { + private retrieveEntries(searchText = null, initModel = false, concatResults = false, isScrolling = false) { this.searchText = searchText; let search$: Observable>>; if (searchText) { const searchPageInfo = Object.assign(new PageInfo(), { elementsPerPage: this.pageInfo.elementsPerPage, - currentPage: 1, + currentPage: isScrolling ? this.pageInfo.currentPage : 1, totalElements: this.pageInfo.totalElements, totalPages: this.pageInfo.totalPages }); search$ = this.vocabularyService.getVocabularyEntriesByValue(this.searchText, false, this.model.vocabularyOptions, From deece4014edbfb50080c679d783ac87a5ce047f4 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Thu, 23 May 2024 16:29:42 +0200 Subject: [PATCH 24/40] [dsc-1516] Adds ROR Country label --- 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 0b213a87ac1..53510f8fe80 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2479,6 +2479,8 @@ "form.other-information.ror_orgunit_acronym": "ROR acronym", + "form.other-information.ror_orgunit_countryName": "ROR country", + "form.entry.source.local": "", "form.entry.source.orcid": "", From 424619930ede8bd60e575f633bd141d50dae2f68 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 28 May 2024 12:49:33 +0200 Subject: [PATCH 25/40] [DSC-1594] - Fixed cache issue + resolved PR comments --- .../edit-bitstream-page.component.ts | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index 7827f184ffd..4b5b4b75124 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -26,10 +26,11 @@ import { Item } from '../../core/shared/item.model'; import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model'; import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; -import {VocabularyService} from '../../core/submission/vocabularies/vocabulary.service'; -import {VocabularyOptions} from '../../core/submission/vocabularies/models/vocabulary-options.model'; -import {PageInfo} from '../../core/shared/page-info.model'; -import {VocabularyEntry} from '../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { VocabularyService } from '../../core/submission/vocabularies/vocabulary.service'; +import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { RequestService } from '../../core/data/request.service'; @Component({ selector: 'ds-edit-bitstream-page', @@ -405,6 +406,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ private bundle: Bundle; + /** + * Options for the vocabulary service + */ + readonly vocabularyOptions = new VocabularyOptions('truefalse', 'bitstream.hide'); + constructor(private route: ActivatedRoute, private router: Router, private changeDetectorRef: ChangeDetectorRef, @@ -416,7 +422,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { private notificationsService: NotificationsService, private bitstreamFormatService: BitstreamFormatDataService, private primaryBitstreamService: PrimaryBitstreamService, - private vocabularyService: VocabularyService + private vocabularyService: VocabularyService, + private requestService: RequestService ) { } @@ -458,12 +465,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { switchMap((bundle: Bundle) => bundle.item), getFirstSucceededRemoteDataPayload(), ); - const vocabularyOptions = new VocabularyOptions( - 'truefalse', - 'bitstream.hide', - ); - const pageInfo = new PageInfo(); - const hide$: Observable = this.vocabularyService.getVocabularyEntries(vocabularyOptions, pageInfo).pipe( + + const hide$: Observable = this.vocabularyService.getVocabularyEntries(this.vocabularyOptions, new PageInfo()).pipe( getFirstCompletedRemoteData(), map((rq)=> rq.hasSucceeded ? rq.payload.page : []), ); @@ -496,24 +499,31 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ); } handleHideBitstream(entries: VocabularyEntry[]) { + if (isEmpty(entries)) { return; } - const options = entries.map((entry) => ({label: entry.display, value: entry.value})); - let hideModel = new DynamicSelectModel({ + + this.hideModel = new DynamicSelectModel({ id: 'hide', name: 'hide', - options: options + options: entries.map((entry) => ({label: entry.display, value: entry.value})), }); - this.hideModel = hideModel; this.inputModels.push(this.hideModel); - const groupModel = new DynamicFormGroupModel({ + this.formModel.push(new DynamicFormGroupModel({ id: 'hideContainer', group: [ - hideModel + this.hideModel ] - }); - this.formModel.push(groupModel); + })); + } + + /** + * Invalidates the cache for specific items based on their HREF substrings and ID bitstream. + */ + invalidCacheItem(){ + this.requestService.setStaleByHrefSubstring(this.itemId); + this.requestService.setStaleByHrefSubstring(this.bitstream.id); } /** @@ -647,6 +657,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * Check for changes against the bitstream and send update requests to the REST API */ onSubmit() { + const updatedValues = this.formGroup.getRawValue(); const updatedBitstream = this.formToBitstream(updatedValues); const selectedFormat = this.formats.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat); @@ -728,6 +739,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }) ).subscribe(() => { this.bitstreamService.commitUpdates(); + this.invalidCacheItem(); this.notificationsService.success( this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.title'), this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.content') From c02197db877daf3c18fc2eb99ba4fd228cf60bb0 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 28 May 2024 12:49:53 +0200 Subject: [PATCH 26/40] [DSC-1594] - Adjusted tests --- .../edit-bitstream-page.component.spec.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index 3c9487d3050..aee7e7368bb 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -25,10 +25,11 @@ import { Item } from '../../core/shared/item.model'; import { MetadataValueFilter } from '../../core/shared/metadata.models'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service'; -import {VocabularyService} from '../../core/submission/vocabularies/vocabulary.service'; -import {VocabularyEntry} from '../../core/submission/vocabularies/models/vocabulary-entry.model'; -import {buildPaginatedList} from '../../core/data/paginated-list.model'; -import {PageInfo} from '../../core/shared/page-info.model'; +import { VocabularyService } from '../../core/submission/vocabularies/vocabulary.service'; +import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model'; +import { buildPaginatedList } from '../../core/data/paginated-list.model'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { RequestService } from '../../core/data/request.service'; const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); @@ -62,6 +63,10 @@ describe('EditBitstreamPageComponent', () => { getVocabularyEntries: jasmine.createSpy('getVocabularyEntries'), }); + const mockRequestService = jasmine.createSpyObj('setStaleByHrefSubstring', { + setStaleByHrefSubstring: jasmine.createSpy('setStaleByHrefSubstring'), + }); + beforeEach(() => { bitstreamID = 'current-bitstream-id'; currentPrimary = bitstreamID; @@ -228,6 +233,7 @@ describe('EditBitstreamPageComponent', () => { { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, { provide: VocabularyService, useValue: mockVocabularyService}, + { provide: RequestService, useValue: mockRequestService }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -528,6 +534,7 @@ describe('EditBitstreamPageComponent', () => { {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, { provide: VocabularyService, useValue: mockVocabularyService}, + { provide: RequestService, useValue: mockRequestService }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -659,6 +666,7 @@ describe('EditBitstreamPageComponent', () => { {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, { provide: VocabularyService, useValue: mockVocabularyService}, + { provide: RequestService, useValue: mockRequestService }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -771,6 +779,7 @@ describe('EditBitstreamPageComponent', () => { { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, { provide: VocabularyService, useValue: mockVocabularyService}, + { provide: RequestService, useValue: mockRequestService }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] @@ -901,6 +910,7 @@ describe('EditBitstreamPageComponent', () => { { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: PrimaryBitstreamService, useValue: primaryBitstreamService }, { provide: VocabularyService, useValue: mockVocabularyService}, + { provide: RequestService, useValue: mockRequestService }, ChangeDetectorRef ], schemas: [NO_ERRORS_SCHEMA] From 1730104e0c15c63bcf805616739709283f2a278c Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 28 May 2024 14:47:46 +0200 Subject: [PATCH 27/40] [DSC-1594] - Fix space --- .../edit-bitstream-page/edit-bitstream-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index 4b5b4b75124..2d29717c67f 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -498,8 +498,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { }) ); } - handleHideBitstream(entries: VocabularyEntry[]) { + handleHideBitstream(entries: VocabularyEntry[]) { if (isEmpty(entries)) { return; } From 9b2bcd0b2aef86af6f6b22be31bfa46111921bad Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 28 May 2024 15:18:12 +0200 Subject: [PATCH 28/40] [DSC-1482] - Fix spaces --- .../file-section/full-file-section.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts index f8a89889a91..80b74bc4d9f 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -22,7 +22,7 @@ import { APP_CONFIG } from 'src/config/app-config.interface'; import { environment } from 'src/environments/environment'; import { UUIDService } from '../../../../core/shared/uuid.service'; import { getMockUUIDService } from '../../../../shared/mocks/uuid.service.mock'; -import {Item} from '../../../../core/shared/item.model'; +import { Item } from '../../../../core/shared/item.model'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; describe('FullFileSectionComponent', () => { @@ -111,6 +111,7 @@ describe('FullFileSectionComponent', () => { authorizedDataService.isAuthorized.and.returnValue(observableOf(false)); comp.canDownload(mockBitstream).subscribe(canDownload => expect(canDownload).toBeFalse()); })); + it('canDownload should return an observable with true value, if user is authorized to download bitstream', waitForAsync(() => { authorizedDataService.isAuthorized.and.returnValue(observableOf(true)); comp.canDownload(mockBitstream).subscribe(canDownload => expect(canDownload).toBeTrue()); From 646b2838fcac99ac9967b93281dead2a7063c7b1 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 28 May 2024 18:14:31 +0200 Subject: [PATCH 29/40] [DSC-1706] - Set languages --- src/config/default-app-config.ts | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 87e0d915f7f..0e5afb4b94e 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -296,30 +296,30 @@ export class DefaultAppConfig implements AppConfig { // When set to active, users will be able to switch to the use of this language in the user interface. languages: LangConfig[] = [ { code: 'en', label: 'English', active: true }, - { code: 'ca', label: 'Català', active: true }, - { code: 'cs', label: 'Čeština', active: true }, + { code: 'ca', label: 'Català', active: false }, + { code: 'cs', label: 'Čeština', active: false }, { code: 'de', label: 'Deutsch', active: true }, { code: 'es', label: 'Español', active: true }, { code: 'fr', label: 'Français', active: true }, - { code: 'gd', label: 'Gàidhlig', active: true }, - { code: 'it', label: 'Italiano', active: true }, - { code: 'lv', label: 'Latviešu', active: true }, - { code: 'hu', label: 'Magyar', active: true }, - { code: 'nl', label: 'Nederlands', active: true }, - { code: 'pl', label: 'Polski', active: true }, - { code: 'pt-PT', label: 'Português', active: true }, - { code: 'pt-BR', label: 'Português do Brasil', active: true }, - { code: 'sr-lat', label: 'Srpski (lat)', active: true}, - { code: 'fi', label: 'Suomi', active: true }, - { code: 'sv', label: 'Svenska', active: true }, - { code: 'tr', label: 'Türkçe', active: true }, - { code: 'vi', label: 'Tiếng Việt', active: true }, - { code: 'kk', label: 'Қазақ', active: true }, - { code: 'bn', label: 'বাংলা', active: true }, - { code: 'hi', label: 'हिंदी', active: true}, - { code: 'el', label: 'Ελληνικά', active: true }, - { code: 'sr-cyr', label: 'Српски', active: true}, - { code: 'uk', label: 'Yкраї́нська', active: true} + { code: 'gd', label: 'Gàidhlig', active: false }, + { code: 'it', label: 'Italiano', active: false }, + { code: 'lv', label: 'Latviešu', active: false }, + { code: 'hu', label: 'Magyar', active: false }, + { code: 'nl', label: 'Nederlands', active: false }, + { code: 'pl', label: 'Polski', active: false }, + { code: 'pt-PT', label: 'Português', active: false }, + { code: 'pt-BR', label: 'Português do Brasil', active: false }, + { code: 'sr-lat', label: 'Srpski (lat)', active: false}, + { code: 'fi', label: 'Suomi', active: false }, + { code: 'sv', label: 'Svenska', active: false }, + { code: 'tr', label: 'Türkçe', active: false }, + { code: 'vi', label: 'Tiếng Việt', active: false }, + { code: 'kk', label: 'Қазақ', active: false }, + { code: 'bn', label: 'বাংলা', active: false }, + { code: 'hi', label: 'हिंदी', active: false }, + { code: 'el', label: 'Ελληνικά', active: false }, + { code: 'sr-cyr', label: 'Српски', active: false }, + { code: 'uk', label: 'Yкраї́нська', active: false } ]; // Browse-By Pages From d9fadbfb6484e9ee736c3ad3da1957a10deedc7d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 28 May 2024 19:21:31 +0200 Subject: [PATCH 30/40] [DSC-1570] Init Datadog Rum on CSR only --- src/app/app.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2b6ad96f315..5ce1213318b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -111,7 +111,9 @@ export class AppComponent implements OnInit, AfterViewInit { this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); - this.datadogRumService.initDatadogRum(); + if (isPlatformBrowser(this.platformId)) { + this.datadogRumService.initDatadogRum(); + } } private storeCSSVariables() { From efd31bdd5102db1d9427434d0fe51a3add456ecf Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 29 May 2024 20:21:59 +0200 Subject: [PATCH 31/40] [DSC-1529] Fix errors occurred during SSR --- src/app/core/metadata/metadata.service.spec.ts | 3 ++- src/app/core/metadata/metadata.service.ts | 9 +++++---- src/app/core/services/internal-link.service.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index cdf663ec068..3bd00a3d787 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -123,7 +123,8 @@ xdescribe('MetadataService', () => { appConfig, authorizationService, schemaJsonLDService, - 'browser' + 'browser', + null ); }); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 910a6374de4..638f50fb7be 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -42,7 +42,7 @@ import { getDownloadableBitstream } from '../shared/bitstream.operators'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { SchemaJsonLDService } from './schema-json-ld/schema-json-ld.service'; import { ITEM } from '../shared/item.resource-type'; -import { isPlatformServer } from '@angular/common'; +import { DOCUMENT, isPlatformServer } from '@angular/common'; import { Root } from '../data/root.model'; import { environment } from '../../../environments/environment'; @@ -102,6 +102,7 @@ export class MetadataService { private authorizationService: AuthorizationDataService, private schemaJsonLDService: SchemaJsonLDService, @Inject(PLATFORM_ID) private platformId: any, + @Inject(DOCUMENT) private _document: Document, ) { } @@ -656,11 +657,11 @@ export class MetadataService { } private getMetaTagValue(key: string): string { - return this.currentObject.value.firstMetadataValue(key); + return this.currentObject?.value?.firstMetadataValue(key); } private getFirstMetaTagValue(keys: string[]): string { - return this.currentObject.value.firstMetadataValue(keys); + return this.currentObject?.value?.firstMetadataValue(keys); } private getMetaTagValuesAndCombine(key: string): string { @@ -741,7 +742,7 @@ export class MetadataService { private setGenericPageMetaTags() { - const pageDocumentTitle = document.getElementsByTagName('title')[0].innerText; + const pageDocumentTitle = this._document.getElementsByTagName('title')[0].innerText; const pageUrl = new URLCombiner(this.hardRedirectService.getCurrentOrigin(), this.router.url).toString(); const genericPageOpenGraphType = 'website'; diff --git a/src/app/core/services/internal-link.service.ts b/src/app/core/services/internal-link.service.ts index f6a40e16921..39b6283829a 100644 --- a/src/app/core/services/internal-link.service.ts +++ b/src/app/core/services/internal-link.service.ts @@ -7,7 +7,7 @@ import { NativeWindowRef, NativeWindowService } from './window.service'; */ @Injectable() export class InternalLinkService { - currentURL = this._window.nativeWindow.location.origin; + currentURL = this._window.nativeWindow?.location?.origin; constructor( @Inject(NativeWindowService) protected _window: NativeWindowRef, From 484e75e382ccb656cf33db5bd0db211418cf2d0b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 29 May 2024 20:24:19 +0200 Subject: [PATCH 32/40] [DSC-1570] Fix issue with missing providers for klaro service during SSR --- src/app/app.component.spec.ts | 8 +++ src/app/app.component.ts | 4 +- .../browser-datadog-rum.service.ts | 67 ++++++++++++++++++ .../datadog-rum/datadog-rum.service.spec.ts | 11 +-- .../shared/datadog-rum/datadog-rum.service.ts | 68 ++----------------- .../datadog-rum/server-datadog-rum.service.ts | 11 +++ src/modules/app/browser-app.module.ts | 6 ++ src/modules/app/server-app.module.ts | 8 ++- 8 files changed, 112 insertions(+), 71 deletions(-) create mode 100644 src/app/shared/datadog-rum/browser-datadog-rum.service.ts create mode 100644 src/app/shared/datadog-rum/server-datadog-rum.service.ts diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index e3bec87c4f4..c483f97823f 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -38,6 +38,7 @@ import { of } from 'rxjs'; import { APP_CONFIG } from '../config/app-config.interface'; import { environment } from '../environments/environment'; import { KlaroService } from './shared/cookies/klaro.service'; +import { DatadogRumService } from './shared/datadog-rum/datadog-rum.service'; let comp: AppComponent; let fixture: ComponentFixture; @@ -57,6 +58,7 @@ describe('App component', () => { let breadcrumbsServiceSpy; let routeServiceMock; let klaroServiceSpy: jasmine.SpyObj; + let datadogRumServiceSpy: jasmine.SpyObj; const getDefaultTestBedConf = () => { breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']); @@ -71,6 +73,11 @@ describe('App component', () => { consentsUpdates$: of({}) }); + datadogRumServiceSpy = jasmine.createSpyObj('DatadogRumService', { + initDatadogRum: jasmine.createSpy('initDatadogRum'), + getDatadogRumState: jasmine.createSpy('getDatadogRumState') + }); + return { imports: [ CommonModule, @@ -99,6 +106,7 @@ describe('App component', () => { { provide: RouteService, useValue: routeServiceMock }, { provide: APP_CONFIG, useValue: environment }, { provide: KlaroService, useValue: klaroServiceSpy }, + { provide: DatadogRumService, useValue: datadogRumServiceSpy }, provideMockStore({ initialState }), AppComponent, // RouteService diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 5ce1213318b..2b6ad96f315 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -111,9 +111,7 @@ export class AppComponent implements OnInit, AfterViewInit { this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); - if (isPlatformBrowser(this.platformId)) { - this.datadogRumService.initDatadogRum(); - } + this.datadogRumService.initDatadogRum(); } private storeCSSVariables() { diff --git a/src/app/shared/datadog-rum/browser-datadog-rum.service.ts b/src/app/shared/datadog-rum/browser-datadog-rum.service.ts new file mode 100644 index 00000000000..c44b64fd97a --- /dev/null +++ b/src/app/shared/datadog-rum/browser-datadog-rum.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { datadogRum } from '@datadog/browser-rum'; +import { CookieConsents, KlaroService } from '../cookies/klaro.service'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { createSelector, Store } from '@ngrx/store'; +import { setDatadogRumStatusAction } from './datadog-rum.actions'; +import { DatadogRumState } from './datadog-rum.reducer'; +import { distinctUntilChanged, take } from 'rxjs/operators'; +import { coreSelector } from '../../core/core.selectors'; +import { CoreState } from '../../core/core-state.model'; +import { DatadogRumService } from './datadog-rum.service'; + +@Injectable() +export class BrowserDatadogRumService extends DatadogRumService { + + consentsUpdates$: BehaviorSubject; + datadogRumStateSelector = createSelector(coreSelector, (state: CoreState) => state.datadogRum); + + constructor( + private klaroService: KlaroService, + private store: Store + ) { + super(); + } + + initDatadogRum() { + this.klaroService.watchConsentUpdates(); + this.consentsUpdates$ = this.klaroService.consentsUpdates$; + this.consentsUpdates$.subscribe(savedPreferences => { + this.getDatadogRumState().subscribe((state) => { + if (savedPreferences?.datadog && + environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && + environment.datadogRum?.service && environment.datadogRum?.env) { + if (!state.isInitialized) { + this.store.dispatch(new setDatadogRumStatusAction({ + isInitialized: true, + isRunning: true + })); + datadogRum.init(environment.datadogRum); + } else if (!state.isRunning) { + this.store.dispatch(new setDatadogRumStatusAction({ + isRunning: true + })); + datadogRum.startSessionReplayRecording(); + } + } else { + datadogRum.stopSessionReplayRecording(); + this.store.dispatch(new setDatadogRumStatusAction({ + isRunning: false + })); + } + }); + }); + } + + + getDatadogRumState(): Observable { + return this.store + .select(this.datadogRumStateSelector) + .pipe( + distinctUntilChanged(), + take(1), + ); + } +} + diff --git a/src/app/shared/datadog-rum/datadog-rum.service.spec.ts b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts index 548e13e9290..0aedf8c40d7 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.spec.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.spec.ts @@ -5,9 +5,10 @@ import { CookieConsents, KlaroService } from '../cookies/klaro.service'; import { of } from 'rxjs'; import { environment } from '../../../environments/environment'; import { setDatadogRumStatusAction } from './datadog-rum.actions'; +import { BrowserDatadogRumService } from './browser-datadog-rum.service'; describe('DatadogRumService', () => { - let service: DatadogRumService; + let service: BrowserDatadogRumService; let store: MockStore; let klaroService: KlaroService; let memoizedSelector; @@ -40,12 +41,12 @@ describe('DatadogRumService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ - DatadogRumService, - provideMockStore({initialState}), - {provide: KlaroService, useValue: klaroServiceSpy}, + { provide: DatadogRumService, useClass: BrowserDatadogRumService }, + provideMockStore({ initialState }), + { provide: KlaroService, useValue: klaroServiceSpy }, ] }); - service = TestBed.inject(DatadogRumService); + service = TestBed.inject(DatadogRumService) as BrowserDatadogRumService; store = TestBed.inject(MockStore); memoizedSelector = store.overrideSelector(service.datadogRumStateSelector, initialState.datadogRum); klaroService = TestBed.inject(KlaroService); diff --git a/src/app/shared/datadog-rum/datadog-rum.service.ts b/src/app/shared/datadog-rum/datadog-rum.service.ts index 4ffce1ee73f..f859a19830b 100644 --- a/src/app/shared/datadog-rum/datadog-rum.service.ts +++ b/src/app/shared/datadog-rum/datadog-rum.service.ts @@ -1,67 +1,11 @@ import { Injectable } from '@angular/core'; -import { environment } from '../../../environments/environment'; -import { datadogRum } from '@datadog/browser-rum'; -import { CookieConsents, KlaroService } from '../cookies/klaro.service'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { createSelector, Store } from '@ngrx/store'; -import { setDatadogRumStatusAction } from './datadog-rum.actions'; -import { DatadogRumState } from './datadog-rum.reducer'; -import { distinctUntilChanged, take } from 'rxjs/operators'; -import { coreSelector } from '../../core/core.selectors'; -import { CoreState } from '../../core/core-state.model'; -@Injectable({ - providedIn: 'root' -}) -export class DatadogRumService { +@Injectable() +export abstract class DatadogRumService { - consentsUpdates$: BehaviorSubject; - datadogRumStateSelector = createSelector(coreSelector, (state: CoreState) => state.datadogRum); - - constructor( - private klaroService: KlaroService, - private store: Store - ) { - } - - initDatadogRum() { - this.klaroService.watchConsentUpdates(); - this.consentsUpdates$ = this.klaroService.consentsUpdates$; - this.consentsUpdates$.subscribe(savedPreferences => { - this.getDatadogRumState().subscribe((state) => { - if (savedPreferences?.datadog && - environment.datadogRum?.clientToken && environment.datadogRum?.applicationId && - environment.datadogRum?.service && environment.datadogRum?.env) { - if (!state.isInitialized) { - this.store.dispatch(new setDatadogRumStatusAction({ - isInitialized: true, - isRunning: true - })); - datadogRum.init(environment.datadogRum); - } else if (!state.isRunning) { - this.store.dispatch(new setDatadogRumStatusAction({ - isRunning: true - })); - datadogRum.startSessionReplayRecording(); - } - } else { - datadogRum.stopSessionReplayRecording(); - this.store.dispatch(new setDatadogRumStatusAction({ - isRunning: false - })); - } - }); - }); - } - - - getDatadogRumState(): Observable { - return this.store - .select(this.datadogRumStateSelector) - .pipe( - distinctUntilChanged(), - take(1), - ); - } + /** + * Initializes the service + */ + abstract initDatadogRum(); } diff --git a/src/app/shared/datadog-rum/server-datadog-rum.service.ts b/src/app/shared/datadog-rum/server-datadog-rum.service.ts new file mode 100644 index 00000000000..2f8d074ab90 --- /dev/null +++ b/src/app/shared/datadog-rum/server-datadog-rum.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@angular/core'; +import { DatadogRumService } from './datadog-rum.service'; + +@Injectable() +export class ServerDatadogRumService extends DatadogRumService { + + initDatadogRum() { + return; + } +} + diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index c9ab093c8c0..8d73cde4ed7 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -35,6 +35,8 @@ import { ReferrerService } from '../../app/core/services/referrer.service'; import { BrowserReferrerService } from '../../app/core/services/browser.referrer.service'; import { MathService } from '../../app/core/shared/math.service'; import { ClientMathService } from '../../app/core/shared/client-math.service'; +import { DatadogRumService } from '../../app/shared/datadog-rum/datadog-rum.service'; +import { BrowserDatadogRumService } from '../../app/shared/datadog-rum/browser-datadog-rum.service'; export const REQ_KEY = makeStateKey('req'); @@ -87,6 +89,10 @@ export function getRequest(transferState: TransferState): any { provide: KlaroService, useClass: BrowserKlaroService }, + { + provide: DatadogRumService, + useClass: BrowserDatadogRumService + }, { provide: SubmissionService, useClass: SubmissionService diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 45bc4e05532..82c580eebc4 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -39,6 +39,8 @@ import { ReferrerService } from '../../app/core/services/referrer.service'; import { ServerReferrerService } from '../../app/core/services/server.referrer.service'; import { MathService } from '../../app/core/shared/math.service'; import { ServerMathService } from '../../app/core/shared/server-math.service'; +import { DatadogRumService } from '../../app/shared/datadog-rum/datadog-rum.service'; +import { ServerDatadogRumService } from '../../app/shared/datadog-rum/server-datadog-rum.service'; export function createTranslateLoader(transferState: TransferState) { return new TranslateServerLoader(transferState, 'dist/server/assets/i18n/', '.json'); @@ -121,7 +123,11 @@ export function createTranslateLoader(transferState: TransferState) { { provide: MathService, useClass: ServerMathService - } + }, + { + provide: DatadogRumService, + useClass: ServerDatadogRumService + }, ] }) export class ServerAppModule { From f72c425f5a0f2991b774393e2bc73c1bf4af3af3 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 30 May 2024 09:41:33 +0200 Subject: [PATCH 33/40] Release DSpace-CRIS 2023.02.04 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 747e3c0e1c7..78d434af0d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dspace-angular", - "version": "2023.02.04-SNAPSHOT", + "version": "2023.02.04", "scripts": { "ng": "ng", "config:watch": "nodemon", From a7cd709ab5eb44a520d22b733ce2c9272b0e41e4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 30 May 2024 09:43:22 +0200 Subject: [PATCH 34/40] Prepare next development iteration --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78d434af0d0..3aeab2b6f93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dspace-angular", - "version": "2023.02.04", + "version": "2023.02.05-SNAPSHOT", "scripts": { "ng": "ng", "config:watch": "nodemon", From 88ac61035dd72f7f81d6b1b74282326c24d3aa96 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 29 May 2024 17:44:11 +0200 Subject: [PATCH 35/40] [CST-15124] propagate error to child controllers, handle removal of field --- ...ynamic-form-control-container.component.ts | 26 ++++---- .../models/link/dynamic-link.model.ts | 25 -------- src/app/shared/form/form.component.ts | 64 ++++++++++--------- src/app/shared/form/form.service.ts | 6 +- src/assets/i18n/en.json5 | 2 + 5 files changed, 53 insertions(+), 70 deletions(-) delete mode 100644 src/app/shared/form/builder/ds-dynamic-form-ui/models/link/dynamic-link.model.ts diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index d7d94ebb97f..f77b83ec7b4 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -3,8 +3,7 @@ import { Component, ComponentFactoryResolver, ContentChildren, - EventEmitter, - Inject, + EventEmitter, Inject, Input, NgZone, OnChanges, @@ -59,9 +58,7 @@ import { TranslateService } from '@ngx-translate/core'; import { ReorderableRelationship } from './existing-metadata-list-element/existing-metadata-list-element.component'; import { DYNAMIC_FORM_CONTROL_TYPE_ONEBOX } from './models/onebox/dynamic-onebox.model'; -import { - DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN -} from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model'; +import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model'; import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './models/tag/dynamic-tag.model'; import { DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER } from './models/date-picker/date-picker.model'; import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP } from './models/lookup/dynamic-lookup.model'; @@ -73,9 +70,7 @@ import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component'; import { DsDatePickerComponent } from './models/date-picker/date-picker.component'; import { DsDynamicListComponent } from './models/list/dynamic-list.component'; import { DsDynamicOneboxComponent } from './models/onebox/dynamic-onebox.component'; -import { - DsDynamicScrollableDropdownComponent -} from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component'; +import { DsDynamicScrollableDropdownComponent } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.component'; import { DsDynamicLookupComponent } from './models/lookup/dynamic-lookup.component'; import { DsDynamicFormGroupComponent } from './models/form-group/dynamic-form-group.component'; import { DsDynamicFormArrayComponent } from './models/array-group/dynamic-form-array.component'; @@ -87,9 +82,7 @@ import { CustomSwitchComponent } from './models/custom-switch/custom-switch.comp import { find, map, startWith, switchMap, take } from 'rxjs/operators'; import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { DsDynamicTypeBindRelationService } from './ds-dynamic-type-bind-relation.service'; -import { - DsDynamicRelationInlineGroupComponent -} from './models/relation-inline-group/dynamic-relation-inline-group.components'; +import { DsDynamicRelationInlineGroupComponent } from './models/relation-inline-group/dynamic-relation-inline-group.components'; import { SearchResult } from '../../../search/models/search-result.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; @@ -130,6 +123,9 @@ import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interfac import { itemLinksToFollow } from '../../../utils/relation-query.utils'; import { DynamicConcatModel } from './models/ds-dynamic-concat.model'; import { Metadata } from '../../../../core/shared/metadata.utils'; +import { DsDynamicMarkdownComponent } from './models/markdown/dynamic-markdown.component'; +import { DYNAMIC_FORM_CONTROL_TYPE_MARKDOWN } from './models/markdown/dynamic-markdown.model'; +import { DynamicLinkModel } from './models/ds-dynamic-link.model'; export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type | null { switch (model.type) { @@ -192,6 +188,9 @@ export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type< case DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH: return CustomSwitchComponent; + case DYNAMIC_FORM_CONTROL_TYPE_MARKDOWN: + return DsDynamicMarkdownComponent; + default: return null; } @@ -504,7 +503,6 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo * Unsubscribe from all subscriptions */ ngOnDestroy(): void { - super.ngOnDestroy(); this.subs .filter((sub) => hasValue(sub)) .forEach((sub) => sub.unsubscribe()); @@ -555,13 +553,13 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo addSecurityLevelToMetadata($event) { this.model.securityLevel = $event; this.securityLevel = $event; - if (this.model.parent && this.model.parent instanceof DynamicConcatModel) { + if (this.model.parent && (this.model.parent instanceof DynamicConcatModel || this.model.parent instanceof DynamicLinkModel)) { this.model.parent.securityLevel = $event; } if (this.model.value) { this.model.securityLevel = $event; this.securityLevel = $event; - if (this.model.parent && this.model.parent instanceof DynamicConcatModel) { + if (this.model.parent && (this.model.parent instanceof DynamicConcatModel || this.model.parent instanceof DynamicLinkModel)) { this.model.parent.securityLevel = $event; } this.change.emit( diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/link/dynamic-link.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/link/dynamic-link.model.ts deleted file mode 100644 index f60031df2d0..00000000000 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/link/dynamic-link.model.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AUTOCOMPLETE_OFF, DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core'; -import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-input.model'; - -export const DYNAMIC_FORM_CONTROL_TYPE_LINK = 'LINK'; - -export interface DsDynamicLinkModelConfig extends DsDynamicInputModelConfig { - value?: any; - submissionScope?: string; -} - -export class DynamicLinkModel extends DsDynamicInputModel { - - @serializable() minChars: number; - @serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LINK; - @serializable() submissionScope: string; - - constructor(config: DsDynamicLinkModelConfig, layout?: DynamicFormControlLayout) { - - super(config, layout); - - this.autoComplete = AUTOCOMPLETE_OFF; - this.submissionScope = config.submissionScope; - } - -} diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 9b2af5c9e22..0a4ea6f7e0a 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -28,6 +28,8 @@ import { DynamicRowGroupModel } from './builder/ds-dynamic-form-ui/models/ds-dyn import { DynamicRelationGroupModel } from './builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; +import { DynamicLinkModel } from './builder/ds-dynamic-form-ui/models/ds-dynamic-link.model'; +import { DynamicConcatModel } from './builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model'; export interface MetadataFields { [key: string]: FormFieldMetadataValueObject[] @@ -53,9 +55,9 @@ export class FormComponent implements OnDestroy, OnInit { @Input() displaySubmit = true; /** - * A boolean that indicate if to display form's reset button + * A boolean that indicate if to display form's cancel button */ - @Input() displayReset = true; + @Input() displayCancel = true; /** * A String that indicate the entity type of the item @@ -78,9 +80,9 @@ export class FormComponent implements OnDestroy, OnInit { @Input() submitLabel = 'form.submit'; /** - * i18n key for the reset button + * i18n key for the cancel button */ - @Input() resetLabel = 'form.reset'; + @Input() cancelLabel = 'form.cancel'; /** * An array of DynamicFormControlModel type @@ -348,6 +350,7 @@ export class FormComponent implements OnDestroy, OnInit { removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void { const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as UntypedFormArray; const event = this.getEvent($event, arrayContext, index, 'remove'); + console.log(formArrayControl, event); if (this.formBuilderService.isQualdropGroup(event.model as DynamicFormControlModel) || this.isInlineGroupForm) { // In case of qualdrop value or inline-group remove event must be dispatched before removing the control from array this.removeArrayItem.emit(event); @@ -355,7 +358,12 @@ export class FormComponent implements OnDestroy, OnInit { if (index === 0 && formArrayControl.value?.length === 1) { event.model = cloneDeep(event.model); const fieldId = event.model.id; - formArrayControl.at(0).get(fieldId).setValue(null); + + if (event.model instanceof DynamicLinkModel || event.model instanceof DynamicConcatModel) { + formArrayControl.at(0).get(fieldId).reset(); + } else { + formArrayControl.at(0).get(fieldId).setValue(null); + } } else { this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext); } @@ -424,29 +432,27 @@ export class FormComponent implements OnDestroy, OnInit { private updateMetadataValue(metadataFields: MetadataFields): void { const metadataKeys = hasValue(metadataFields) ? Object.keys(metadataFields) : []; - const formKeys = hasValue(this.formGroup.value) ? Object.keys(this.formGroup.value) : []; - - formKeys - .filter((key) => isNotEmpty(this.formGroup.value[key])) - .forEach((key) => { - const innerObjectKeys = (Object.keys(this.formGroup.value[key]) as any[]).map((oldKey) => oldKey.replaceAll('_', '.')); - const filteredKeys = innerObjectKeys.filter(innerKey => metadataKeys.includes(innerKey)); - const oldValue = this.formGroup.value[key]; - - if (filteredKeys.length > 0) { - filteredKeys.forEach((oldValueKey) => { - const newValue = { ...oldValue }; - const formattedKey = (oldValueKey as any).replaceAll('.', '_'); - const patchValue = {}; - - newValue[formattedKey] = metadataFields[oldValueKey][0]; - patchValue[key] = newValue; - - if (!isEqual(oldValue[oldValueKey], newValue[oldValueKey])) { - this.formGroup.patchValue(patchValue); - } - }); - } - }); + const formKeys = hasValue(this.formGroup.value) ? Object.keys(this.formGroup.value).map(key => key.replace('_array', '')) : []; + + formKeys.forEach((key) => { + const innerObjectKeys = (Object.keys(this.formGroup.value[key] ?? {} ) as any[]).map((oldKey) => oldKey.replaceAll('_', '.')); + const filteredKeys = innerObjectKeys.filter(innerKey => metadataKeys.includes(innerKey)); + const oldValue = this.formGroup.value[key]; + + if (filteredKeys.length > 0) { + filteredKeys.forEach((oldValueKey) => { + const newValue = {...oldValue}; + const formattedKey = (oldValueKey as any).replaceAll('.', '_'); + const patchValue = {}; + + newValue[formattedKey] = metadataFields[oldValueKey][0]; + patchValue[key] = newValue; + + if (!isEqual(oldValue[oldValueKey], newValue[oldValueKey])) { + this.formGroup.patchValue(patchValue); + } + }); + } + }); } } diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index 682c8dc1780..15391fb9223 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -21,6 +21,7 @@ import { } from './form.actions'; import { FormEntry, FormError, FormTouchedState } from './form.reducer'; import { environment } from '../../../environments/environment'; +import { DynamicLinkModel } from './builder/ds-dynamic-form-ui/models/ds-dynamic-link.model'; @Injectable() export class FormService { @@ -137,6 +138,7 @@ export class FormService { } public addErrorToField(field: AbstractControl, model: DynamicFormControlModel, message: string) { + const error = {}; // create the error object const errorKey = this.getValidatorNameFromMap(message); let errorMsg = message; @@ -163,9 +165,9 @@ export class FormService { } // if the field in question is a concat group, pass down the error to its fields - if (field instanceof UntypedFormGroup && model instanceof DynamicFormGroupModel && this.formBuilderService.isConcatGroup(model)) { + if ((field instanceof UntypedFormGroup && model instanceof DynamicFormGroupModel && this.formBuilderService.isConcatGroup(model)) || model instanceof DynamicLinkModel) { model.group.forEach((subModel) => { - const subField = field.controls[subModel.id]; + const subField = (field as UntypedFormGroup).controls[subModel.id]; this.addErrorToField(subField, subModel, message); }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 9d9d5716468..9532bf14ec0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2143,6 +2143,8 @@ "error.validation.groupExists": "This group already exists", + "error.validation.invalidProjectURL": "The URL of the project is invalid", + "error.validation.metadata.name.invalid-pattern": "This field cannot contain dots, commas or spaces. Please use the Element & Qualifier fields instead", "error.validation.metadata.name.max-length": "This field may not contain more than 32 characters", From d268681bd51deaad37a33d650b8cf0106fb31b78 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 31 May 2024 09:18:02 +0200 Subject: [PATCH 36/40] [DSC-1735] fix cherry-pick conflicts --- ...ynamic-form-control-container.component.ts | 8 +--- src/app/shared/form/form.component.ts | 47 ++++++++++--------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index f77b83ec7b4..e363ba0d60d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -123,9 +123,7 @@ import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interfac import { itemLinksToFollow } from '../../../utils/relation-query.utils'; import { DynamicConcatModel } from './models/ds-dynamic-concat.model'; import { Metadata } from '../../../../core/shared/metadata.utils'; -import { DsDynamicMarkdownComponent } from './models/markdown/dynamic-markdown.component'; -import { DYNAMIC_FORM_CONTROL_TYPE_MARKDOWN } from './models/markdown/dynamic-markdown.model'; -import { DynamicLinkModel } from './models/ds-dynamic-link.model'; +import { DynamicLinkModel } from "./models/ds-dynamic-link.model"; export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type | null { switch (model.type) { @@ -188,9 +186,6 @@ export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type< case DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH: return CustomSwitchComponent; - case DYNAMIC_FORM_CONTROL_TYPE_MARKDOWN: - return DsDynamicMarkdownComponent; - default: return null; } @@ -503,6 +498,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo * Unsubscribe from all subscriptions */ ngOnDestroy(): void { + super.ngOnDestroy(); this.subs .filter((sub) => hasValue(sub)) .forEach((sub) => sub.unsubscribe()); diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 0a4ea6f7e0a..1bab0b414b3 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -55,9 +55,9 @@ export class FormComponent implements OnDestroy, OnInit { @Input() displaySubmit = true; /** - * A boolean that indicate if to display form's cancel button + * A boolean that indicate if to display form's reset button */ - @Input() displayCancel = true; + @Input() displayReset = true; /** * A String that indicate the entity type of the item @@ -82,7 +82,7 @@ export class FormComponent implements OnDestroy, OnInit { /** * i18n key for the cancel button */ - @Input() cancelLabel = 'form.cancel'; + @Input() resetLabel = 'form.reset'; /** * An array of DynamicFormControlModel type @@ -350,7 +350,6 @@ export class FormComponent implements OnDestroy, OnInit { removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void { const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as UntypedFormArray; const event = this.getEvent($event, arrayContext, index, 'remove'); - console.log(formArrayControl, event); if (this.formBuilderService.isQualdropGroup(event.model as DynamicFormControlModel) || this.isInlineGroupForm) { // In case of qualdrop value or inline-group remove event must be dispatched before removing the control from array this.removeArrayItem.emit(event); @@ -434,25 +433,27 @@ export class FormComponent implements OnDestroy, OnInit { const metadataKeys = hasValue(metadataFields) ? Object.keys(metadataFields) : []; const formKeys = hasValue(this.formGroup.value) ? Object.keys(this.formGroup.value).map(key => key.replace('_array', '')) : []; - formKeys.forEach((key) => { - const innerObjectKeys = (Object.keys(this.formGroup.value[key] ?? {} ) as any[]).map((oldKey) => oldKey.replaceAll('_', '.')); - const filteredKeys = innerObjectKeys.filter(innerKey => metadataKeys.includes(innerKey)); - const oldValue = this.formGroup.value[key]; - - if (filteredKeys.length > 0) { - filteredKeys.forEach((oldValueKey) => { - const newValue = {...oldValue}; - const formattedKey = (oldValueKey as any).replaceAll('.', '_'); - const patchValue = {}; - - newValue[formattedKey] = metadataFields[oldValueKey][0]; - patchValue[key] = newValue; - - if (!isEqual(oldValue[oldValueKey], newValue[oldValueKey])) { - this.formGroup.patchValue(patchValue); - } - }); - } + formKeys + .filter((key) => isNotEmpty(this.formGroup.value[key])) + .forEach((key) => { + const innerObjectKeys = (Object.keys(this.formGroup.value[key] ?? {} ) as any[]).map((oldKey) => oldKey.replaceAll('_', '.')); + const filteredKeys = innerObjectKeys.filter(innerKey => metadataKeys.includes(innerKey)); + const oldValue = this.formGroup.value[key]; + + if (filteredKeys.length > 0) { + filteredKeys.forEach((oldValueKey) => { + const newValue = {...oldValue}; + const formattedKey = (oldValueKey as any).replaceAll('.', '_'); + const patchValue = {}; + + newValue[formattedKey] = metadataFields[oldValueKey][0]; + patchValue[key] = newValue; + + if (!isEqual(oldValue[oldValueKey], newValue[oldValueKey])) { + this.formGroup.patchValue(patchValue); + } + }); + } }); } } From 76d0daa46778bbe8a57024b0c3eac9801c163e6c Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 31 May 2024 10:12:10 +0200 Subject: [PATCH 37/40] [DSC-1735] fix lint --- .../ds-dynamic-form-control-container.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index e363ba0d60d..f1e7be939ac 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -123,7 +123,7 @@ import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interfac import { itemLinksToFollow } from '../../../utils/relation-query.utils'; import { DynamicConcatModel } from './models/ds-dynamic-concat.model'; import { Metadata } from '../../../../core/shared/metadata.utils'; -import { DynamicLinkModel } from "./models/ds-dynamic-link.model"; +import { DynamicLinkModel } from './models/ds-dynamic-link.model'; export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type | null { switch (model.type) { From f923114c4cd8576e23c8104ae1009ad0f64e9414 Mon Sep 17 00:00:00 2001 From: Simone Ramundi Date: Tue, 4 Jun 2024 17:28:27 +0200 Subject: [PATCH 38/40] [DSC-1748] Reinstated previous class as 'container' --- src/app/item-page/edit-item-page/edit-item-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/edit-item-page.component.html b/src/app/item-page/edit-item-page/edit-item-page.component.html index 5b80b573f13..efee33e9cc9 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.component.html +++ b/src/app/item-page/edit-item-page/edit-item-page.component.html @@ -1,4 +1,4 @@ -
+

{{'item.edit.head' | translate}}

From b94cb0a4a8bc15802650997da9d4200d1dfe9d15 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 10 Jun 2024 11:44:29 +0200 Subject: [PATCH 39/40] [DSC-1759] navbar-search in mobile screen fix --- src/app/search-navbar/search-navbar.component.scss | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/app/search-navbar/search-navbar.component.scss b/src/app/search-navbar/search-navbar.component.scss index f92dd113d00..7eb43c84752 100644 --- a/src/app/search-navbar/search-navbar.component.scss +++ b/src/app/search-navbar/search-navbar.component.scss @@ -21,15 +21,9 @@ input[type="text"] { } @media screen and (max-width: map-get($grid-breakpoints, md)) { - .query:focus { + .search-input { max-width: 250px !important; width: 40vw !important; } } - -@media screen and (max-width: map-get($grid-breakpoints, sm)) { - .search-input { - width: 55vw !important; - } -} From 6bd188cecf1498ce6080d142979676b8e6a27c6b Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Tue, 11 Jun 2024 08:44:06 +0200 Subject: [PATCH 40/40] [DSC-1747] restore thumbnails in search pages --- .../search-page/configuration-search-page.component.ts | 6 ++++-- src/app/shared/search/search.component.spec.ts | 6 ++++++ src/app/shared/search/search.component.ts | 8 ++++++-- src/app/shared/search/themed-search.component.ts | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/app/search-page/configuration-search-page.component.ts b/src/app/search-page/configuration-search-page.component.ts index 7c8f76e6487..bb558b1f14d 100644 --- a/src/app/search-page/configuration-search-page.component.ts +++ b/src/app/search-page/configuration-search-page.component.ts @@ -9,6 +9,7 @@ import { RouteService } from '../core/services/route.service'; import { SearchService } from '../core/shared/search/search.service'; import { Router } from '@angular/router'; import { SearchManager } from '../core/browse/search-manager'; +import { APP_CONFIG, AppConfig } from '../../config/app-config.interface'; /** * This component renders a search page using a configuration as input. @@ -35,7 +36,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent { @Inject(PLATFORM_ID) public platformId: any, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, protected routeService: RouteService, - protected router: Router) { - super(service, searchManager, sidebarService, windowService, searchConfigService, platformId, routeService, router); + protected router: Router, + @Inject(APP_CONFIG) protected appConfig: AppConfig,) { + super(service, searchManager, sidebarService, windowService, searchConfigService, platformId, routeService, router, appConfig); } } diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index 01f6f1c1884..6e7de413551 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -34,6 +34,8 @@ import { SearchFilterConfig } from './models/search-filter-config.model'; import { FilterType } from './models/filter-type.model'; import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths'; import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths'; +import { APP_CONFIG } from '../../../config/app-config.interface'; +import { environment } from '../../../environments/environment'; let comp: SearchComponent; let fixture: ComponentFixture; @@ -239,6 +241,10 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar { provide: SEARCH_CONFIG_SERVICE, useValue: searchConfigurationServiceStub + }, + { + provide: APP_CONFIG, + useValue: environment } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 158324b0b92..b7c3bbd473c 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -51,6 +51,7 @@ import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routi import { SearchManager } from '../../core/browse/search-manager'; import { AlertType } from '../alert/alert-type'; import { isPlatformServer } from '@angular/common'; +import { APP_CONFIG } from '../../../config/app-config.interface'; @Component({ selector: 'ds-search', @@ -187,7 +188,7 @@ export class SearchComponent implements OnInit, OnDestroy { /** * Whether to show the thumbnail preview */ - @Input() showThumbnails; + @Input() showThumbnails: boolean; /** * Whether to show the view mode switch @@ -368,7 +369,8 @@ export class SearchComponent implements OnInit, OnDestroy { @Inject(PLATFORM_ID) public platformId: any, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, protected routeService: RouteService, - protected router: Router,) { + protected router: Router, + @Inject(APP_CONFIG) protected appConfig: any,){ this.isXsOrSm$ = this.windowService.isXsOrSm(); } @@ -385,6 +387,8 @@ export class SearchComponent implements OnInit, OnDestroy { return; } + this.showThumbnails = this.showThumbnails ?? this.appConfig.browseBy.showThumbnails; + if (this.useUniquePageId) { // Create an unique pagination id related to the instance of the SearchComponent this.paginationId = uniqueId(this.paginationId); diff --git a/src/app/shared/search/themed-search.component.ts b/src/app/shared/search/themed-search.component.ts index 710bcc1071a..0463ed62aa2 100644 --- a/src/app/shared/search/themed-search.component.ts +++ b/src/app/shared/search/themed-search.component.ts @@ -68,7 +68,7 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() showSidebar: boolean; - @Input() showThumbnails; + @Input() showThumbnails: boolean; @Input() showViewModes: boolean;