From 48ad0fc8ae67f0d0aae5d586efadb52aa3316f04 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 31 Dec 2021 11:03:47 +0530 Subject: [PATCH 001/167] [DSC-337] boxes flagged as minor appears in tabs even when are the only available. --- .../cris-layout-metadata-box.component.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html index 9464101311f..f7b031ee659 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html @@ -1,10 +1,12 @@
-
+
+
+
From d5e41e559c52113466e69bd031514f49478af04f Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 4 Jan 2022 12:16:23 +0530 Subject: [PATCH 002/167] [DSC-337] hide the tab when it's all the box have minor. --- .../cris-layout-metadata-box.component.html | 14 ++++----- src/app/cris-layout/cris-layout.component.ts | 29 +++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html index f7b031ee659..9464101311f 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/cris-layout-metadata-box.component.html @@ -1,12 +1,10 @@
-
-
-
+
diff --git a/src/app/cris-layout/cris-layout.component.ts b/src/app/cris-layout/cris-layout.component.ts index ac1ba0198f7..e952f634a53 100644 --- a/src/app/cris-layout/cris-layout.component.ts +++ b/src/app/cris-layout/cris-layout.component.ts @@ -76,7 +76,7 @@ export class CrisLayoutComponent implements OnInit { */ getLeadingTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => tab.leading)), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => this.checkForMinor(tab,tab.leading))), ); } @@ -85,7 +85,7 @@ export class CrisLayoutComponent implements OnInit { */ getLoaderTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => !tab.leading)), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => this.checkForMinor(tab,!tab.leading))), ); } @@ -98,4 +98,29 @@ export class CrisLayoutComponent implements OnInit { ); } + /** + * + * @param tab Contains a tab data which has rows, cells and boxes + * @param isLeading Contains a boolean + * @returns Boolean based on cells has minor or not + */ + checkForMinor(tab: CrisLayoutTab,isLeading: boolean): boolean { + if (isLeading) { + let isMinor = true; + for (const row of tab.rows) { + rowLoop: + for (const cell of row.cells) { + for (const box of cell.boxes) { + if (!box.minor) { + isMinor = false; + break rowLoop; + } + } + } + } + return !isMinor; + } + return false; + } + } From f03d3fecf293a1ba55c30161f62ddd99067501c6 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 4 Jan 2022 16:40:22 +0530 Subject: [PATCH 003/167] [DSC-337] code refactor. --- src/app/cris-layout/cris-layout.component.ts | 30 +++++++------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/app/cris-layout/cris-layout.component.ts b/src/app/cris-layout/cris-layout.component.ts index e952f634a53..26d751132d7 100644 --- a/src/app/cris-layout/cris-layout.component.ts +++ b/src/app/cris-layout/cris-layout.component.ts @@ -76,7 +76,7 @@ export class CrisLayoutComponent implements OnInit { */ getLeadingTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => this.checkForMinor(tab,tab.leading))), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => tab.leading).filter(tab => this.checkForMinor(tab))), ); } @@ -85,7 +85,7 @@ export class CrisLayoutComponent implements OnInit { */ getLoaderTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => this.checkForMinor(tab,!tab.leading))), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => !tab.leading ).filter(tab => this.checkForMinor(tab))), ); } @@ -99,28 +99,20 @@ export class CrisLayoutComponent implements OnInit { } /** - * * @param tab Contains a tab data which has rows, cells and boxes - * @param isLeading Contains a boolean * @returns Boolean based on cells has minor or not */ - checkForMinor(tab: CrisLayoutTab,isLeading: boolean): boolean { - if (isLeading) { - let isMinor = true; - for (const row of tab.rows) { - rowLoop: - for (const cell of row.cells) { - for (const box of cell.boxes) { - if (!box.minor) { - isMinor = false; - break rowLoop; - } - } + checkForMinor(tab: CrisLayoutTab): boolean { + for (const row of tab.rows) { + for (const cell of row.cells) { + for (const box of cell.boxes) { + if (!box.minor) { + return true; } } - return !isMinor; - } - return false; + } } + return false; +} } From fd834463ea4edb4ed41f56cc8345439a2c69ddc5 Mon Sep 17 00:00:00 2001 From: mushrankhan-kencor <83634419+mushrankhan-kencor@users.noreply.github.com> Date: Tue, 1 Feb 2022 14:02:04 +0530 Subject: [PATCH 004/167] [DSC-337] test case added for minor element. --- .../cris-layout/cris-layout.component.spec.ts | 13 +- src/app/shared/testing/layout-tab.mocks.ts | 140 ++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/app/cris-layout/cris-layout.component.spec.ts b/src/app/cris-layout/cris-layout.component.spec.ts index d7ec238dab6..af5cfa1aeda 100644 --- a/src/app/cris-layout/cris-layout.component.spec.ts +++ b/src/app/cris-layout/cris-layout.component.spec.ts @@ -39,7 +39,7 @@ const tabDataServiceMock: any = jasmine.createSpyObj('TabDataService', { // to FIX // tslint:disable-next-line:prefer-const -describe('CrisLayoutComponent', () => { +fdescribe('CrisLayoutComponent', () => { let component: CrisLayoutComponent; let fixture: ComponentFixture; @@ -153,6 +153,17 @@ describe('CrisLayoutComponent', () => { }); + it('it should not return a tab if box cointain a minor as true', () => { + tabDataServiceMock.findByItem.and.returnValue(observableOf(bothTabs)); + component.tabs$ = observableOf(bothTabs); + component.leadingTabs$ = observableOf(leadingTabs); + component.loaderTabs$ = observableOf(loaderTabs); + component.getLeadingTabs(); + fixture.detectChanges(); + const loaderTabsData = fixture.debugElement.queryAll(By.css('ds-cris-layout-loader')); + expect(loaderTabsData.length).toBe(1); + }); + }); }); diff --git a/src/app/shared/testing/layout-tab.mocks.ts b/src/app/shared/testing/layout-tab.mocks.ts index 9e618cda241..2fb1a852ec0 100644 --- a/src/app/shared/testing/layout-tab.mocks.ts +++ b/src/app/shared/testing/layout-tab.mocks.ts @@ -347,6 +347,146 @@ export const loaderTabs: CrisLayoutTab[] = [Object.assign(new CrisLayoutTab(), { ] } ] +}), +Object.assign(new CrisLayoutTab(), { + 'id': 3, + 'shortname': 'info', + 'header': 'Profile', + 'entityType': 'Person', + 'priority': 1, + 'security': 0, + 'type': 'tab', + 'leading': false, + 'rows': [ + { + 'style': 'col-md-12', + 'cells': [ + { + 'style': 'col-md-6', + 'boxes': [ + { + 'shortname': 'primary', + 'header': 'Primary Information', + 'entityType': 'Person', + 'collapsed': false, + 'minor': false, + 'style': 'col-md-6', + 'clear': true, + 'container': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'METADATA', + 'type': 'box', + 'metadataSecurityFields': [], + 'configuration': { + 'id': 1, + 'rows': [ + { + 'fields': [ + { + 'metadata': 'dc.title', + 'label': 'Name', + 'fieldType': 'metadata' + }, + { + 'metadata': 'person.email', + 'label': 'Email', + 'fieldType': 'metadata', + 'valuesInline': 'true' + } + ] + } + ] + } + }, + { + 'shortname': 'other', + 'header': 'Other Informations', + 'entityType': 'Person', + 'collapsed': false, + 'minor': true, + 'style': 'col-md-6', + 'clear': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'METADATA', + 'type': 'box', + 'metadataSecurityFields': [ + 'cris.policy.eperson' + ], + 'configuration': { + 'id': 2, + 'rows': [ + { + 'fields': [ + { + 'metadata': 'person.birthDate', + 'label': 'Birth date', + 'fieldType': 'metadata', + 'labelAsHeading': 'true' + } + ] + } + ] + } + } + ] + }, + { + 'style': 'col-md-6', + 'boxes': [ + { + 'shortname': 'researchoutputs', + 'header': 'Research outputs', + 'entityType': 'Person', + 'collapsed': false, + 'minor': false, + 'style': 'col-md-6', + 'clear': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'RELATION', + 'type': 'box', + 'metadataSecurityFields': [], + 'configuration': { + 'id': 3, + 'discovery-configuration': 'RELATION.Person.researchoutputs' + } + } + ] + } + ] + }, + { + 'style': 'col-md-12', + 'cells': [ + { + 'style': 'col-md-12', + 'boxes': [ + { + 'shortname': 'metrics', + 'header': 'Metrics', + 'entityType': 'Person', + 'collapsed': false, + 'minor': false, + 'style': null, + 'clear': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'METRICS', + 'type': 'box', + 'metadataSecurityFields': [], + 'configuration': { + 'id': 4, + 'numColumns': 2, + 'metrics': ['views', 'downloads'] + } + } + ] + } + ] + } + ] }) ]; From 3319613fb2d36caa86c077b659040d935842751d Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Mon, 7 Feb 2022 13:37:37 +0530 Subject: [PATCH 005/167] [DSC-337] row added in loaderTabs. --- src/app/shared/testing/layout-tab.mocks.ts | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/app/shared/testing/layout-tab.mocks.ts b/src/app/shared/testing/layout-tab.mocks.ts index 2fb1a852ec0..657142946b5 100644 --- a/src/app/shared/testing/layout-tab.mocks.ts +++ b/src/app/shared/testing/layout-tab.mocks.ts @@ -485,6 +485,54 @@ Object.assign(new CrisLayoutTab(), { ] } ] + }, + { + 'style': 'col-md-12', + 'cells': [ + { + 'style': 'col-md-12', + 'boxes': [ + { + 'shortname': 'metrics', + 'header': 'Metrics', + 'entityType': 'Person', + 'collapsed': false, + 'minor': false, + 'style': null, + 'clear': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'METRICS', + 'type': 'box', + 'metadataSecurityFields': [], + 'configuration': { + 'id': 4, + 'numColumns': 2, + 'metrics': ['views', 'downloads'] + } + }, + { + 'shortname': 'metrics', + 'header': 'Metrics', + 'entityType': 'Person', + 'collapsed': false, + 'minor': true, + 'style': null, + 'clear': true, + 'maxColumn': 2, + 'security': 0, + 'boxType': 'METRICS', + 'type': 'box', + 'metadataSecurityFields': [], + 'configuration': { + 'id': 4, + 'numColumns': 2, + 'metrics': ['views', 'downloads'] + } + } + ] + } + ] } ] }) From 6d6253b35498bd3bc8bd0f83ceeecc1193796302 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Mon, 7 Feb 2022 13:43:12 +0530 Subject: [PATCH 006/167] [DSC-337] remove fdescribe. --- src/app/cris-layout/cris-layout.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/cris-layout/cris-layout.component.spec.ts b/src/app/cris-layout/cris-layout.component.spec.ts index af5cfa1aeda..804aa9248fd 100644 --- a/src/app/cris-layout/cris-layout.component.spec.ts +++ b/src/app/cris-layout/cris-layout.component.spec.ts @@ -39,7 +39,7 @@ const tabDataServiceMock: any = jasmine.createSpyObj('TabDataService', { // to FIX // tslint:disable-next-line:prefer-const -fdescribe('CrisLayoutComponent', () => { +describe('CrisLayoutComponent', () => { let component: CrisLayoutComponent; let fixture: ComponentFixture; From ce0b993cfee3e403c1bd5dbe7c5a547918dc4b12 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 22 Mar 2022 18:00:21 +0530 Subject: [PATCH 007/167] [DSC-337] check for minor element is move to tab data service. --- src/app/core/layout/tab-data.service.ts | 36 +++++++++++++++++-- src/app/cris-layout/cris-layout.component.ts | 21 ++--------- .../item-page/cris-item-page-tab.resolver.ts | 1 + 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/app/core/layout/tab-data.service.ts b/src/app/core/layout/tab-data.service.ts index 2d19df11056..fa5e57c200a 100644 --- a/src/app/core/layout/tab-data.service.ts +++ b/src/app/core/layout/tab-data.service.ts @@ -19,6 +19,7 @@ import { PaginatedList } from '../data/paginated-list.model'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; +import { map } from 'rxjs/operators'; /* tslint:disable:max-classes-per-file */ @@ -75,10 +76,41 @@ export class TabDataService { * @param useCachedVersionIfAvailable * @param linkToFollow */ - findByItem(itemUuid: string, useCachedVersionIfAvailable, linkToFollow?: FollowLinkConfig): Observable>> { + findByItem(itemUuid: string, useCachedVersionIfAvailable, excludeMinors?: boolean ,linkToFollow?: FollowLinkConfig): Observable>> { const options = new FindListOptions(); options.searchParams = [new RequestParam('uuid', itemUuid)]; - return this.dataService.searchBy(this.searchFindByItem, options, useCachedVersionIfAvailable); + + return this.dataService.searchBy(this.searchFindByItem, options, useCachedVersionIfAvailable).pipe(map((data) => { + if (!!data.payload && !!data.payload.page && excludeMinors) { + data.payload.page = this.filterTab(data.payload.page); + } + return data; + })); + } + + /** + * @param tabs + * @returns Tabs which contains non minor element + */ + filterTab(tabs: CrisLayoutTab[]): CrisLayoutTab[] { + return tabs.filter(tab => this.checkForMinor(tab)); + } + + /** + * @param tab Contains a tab data which has rows, cells and boxes + * @returns Boolean based on cells has minor or not + */ + checkForMinor(tab: CrisLayoutTab): boolean { + for (const row of tab.rows) { + for (const cell of row.cells) { + for (const box of cell.boxes) { + if (!box.minor) { + return true; + } + } + } + } + return false; } /** diff --git a/src/app/cris-layout/cris-layout.component.ts b/src/app/cris-layout/cris-layout.component.ts index 1e88fe681a9..8b999950d9d 100644 --- a/src/app/cris-layout/cris-layout.component.ts +++ b/src/app/cris-layout/cris-layout.component.ts @@ -84,7 +84,7 @@ export class CrisLayoutComponent implements OnInit { */ getLeadingTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => tab.leading).filter(tab => this.checkForMinor(tab))), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => tab.leading)), ); } @@ -93,7 +93,7 @@ export class CrisLayoutComponent implements OnInit { */ getLoaderTabs(): Observable { return this.tabs$.pipe( - map((tabs: CrisLayoutTab[]) => tabs.filter(tab => !tab.leading ).filter(tab => this.checkForMinor(tab))), + map((tabs: CrisLayoutTab[]) => tabs.filter(tab => !tab.leading)), ); } @@ -106,21 +106,4 @@ export class CrisLayoutComponent implements OnInit { ); } - /** - * @param tab Contains a tab data which has rows, cells and boxes - * @returns Boolean based on cells has minor or not - */ - checkForMinor(tab: CrisLayoutTab): boolean { - for (const row of tab.rows) { - for (const cell of row.cells) { - for (const box of cell.boxes) { - if (!box.minor) { - return true; - } - } - } - } - return false; -} - } diff --git a/src/app/item-page/cris-item-page-tab.resolver.ts b/src/app/item-page/cris-item-page-tab.resolver.ts index fc7aaa5f9e0..f1de1265bd6 100644 --- a/src/app/item-page/cris-item-page-tab.resolver.ts +++ b/src/app/item-page/cris-item-page-tab.resolver.ts @@ -44,6 +44,7 @@ export class CrisItemPageTabResolver implements Resolve this.tabService.findByItem( item.uuid, // Item UUID + true, true ).pipe( getFirstCompletedRemoteData(), From f6b38d4b233ee2287f847d0f9cf63d74e66ec2ea Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 22 Mar 2022 18:11:47 +0530 Subject: [PATCH 008/167] [DSC-337] remove test case for checking minor element from component. --- src/app/cris-layout/cris-layout.component.spec.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/app/cris-layout/cris-layout.component.spec.ts b/src/app/cris-layout/cris-layout.component.spec.ts index 3962f17c7c9..a428fb95e18 100644 --- a/src/app/cris-layout/cris-layout.component.spec.ts +++ b/src/app/cris-layout/cris-layout.component.spec.ts @@ -156,17 +156,6 @@ describe('CrisLayoutComponent', () => { }); - it('it should not return a tab if box cointain a minor as true', () => { - tabDataServiceMock.findByItem.and.returnValue(observableOf(bothTabs)); - component.tabs$ = observableOf(bothTabs); - component.leadingTabs$ = observableOf(leadingTabs); - component.loaderTabs$ = observableOf(loaderTabs); - component.getLeadingTabs(); - fixture.detectChanges(); - const loaderTabsData = fixture.debugElement.queryAll(By.css('ds-cris-layout-loader')); - expect(loaderTabsData.length).toBe(1); - }); - }); }); From e0d6d4067aee1a734be0766bb0ef71e2a8530a50 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 23 Mar 2022 12:06:50 +0530 Subject: [PATCH 009/167] [DSC-337] add test cases for filterTab. --- src/app/core/layout/tab-data.service.spec.ts | 9 +++++++++ src/app/core/layout/tab-data.service.ts | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app/core/layout/tab-data.service.spec.ts b/src/app/core/layout/tab-data.service.spec.ts index 30995e1f53e..4e755f0c0ab 100644 --- a/src/app/core/layout/tab-data.service.spec.ts +++ b/src/app/core/layout/tab-data.service.spec.ts @@ -16,6 +16,7 @@ import { of } from 'rxjs'; import { FindListOptions } from '../data/request.models'; import { RequestParam } from '../cache/models/request-param.model'; import { createPaginatedList } from '../../shared/testing/utils.test'; +import { bothTabs } from '../../shared/testing/layout-tab.mocks'; describe('TabDataService', () => { let scheduler: TestScheduler; @@ -193,4 +194,12 @@ describe('TabDataService', () => { }); }); + + + fdescribe('filterTab', () => { + it('should return non minor element', () => { + const tabs: CrisLayoutTab[] = service.filterTab(bothTabs); + expect(tabs.length).toBe(2); + }); + }); }); diff --git a/src/app/core/layout/tab-data.service.ts b/src/app/core/layout/tab-data.service.ts index fa5e57c200a..3091160156e 100644 --- a/src/app/core/layout/tab-data.service.ts +++ b/src/app/core/layout/tab-data.service.ts @@ -104,13 +104,13 @@ export class TabDataService { for (const row of tab.rows) { for (const cell of row.cells) { for (const box of cell.boxes) { - if (!box.minor) { - return true; + if (box.minor) { + return false; } } } } - return false; + return true; } /** From 94f07f316dd49fcb93664ba757ea76ed0c04559b Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Thu, 2 Jun 2022 13:16:27 +0530 Subject: [PATCH 010/167] DSC-38 changes for map component should be enhanced in order to give the possibility of exporting the map as png and jpg/jpeg format. --- .../statistics-map.component.html | 14 ++++++- .../statistics-map.component.spec.ts | 29 ++++++++++++++- .../statistics-map.component.ts | 37 ++++++++++++++++++- ...bmission-import-external.component.spec.ts | 2 +- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html index 83cb9fc6f2c..62934b8924c 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html @@ -1 +1,13 @@ - +
+ + +
+
+ +
diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts index 903b61374fe..e417f76631c 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts @@ -5,6 +5,9 @@ import { UsageReport } from '../../../core/statistics/models/usage-report.model' import { USAGE_REPORT } from '../../../core/statistics/models/usage-report.resource-type'; import { GoogleChartInterface } from 'ng2-google-charts'; +import { ExportService, ExportImageType } from '../../../core/export-service/export.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; describe('StatisticsMapComponent', () => { let component: StatisticsMapComponent; @@ -49,9 +52,17 @@ describe('StatisticsMapComponent', () => { options: { 'title': 'TopCountries' }, }; + const exportServiceMock: any = { + exportAsImage: jasmine.createSpy('exportAsImage'), + exportAsFile: jasmine.createSpy('exportAsFile') + }; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ StatisticsMapComponent ] + imports: [TranslateModule.forRoot()], + declarations: [ StatisticsMapComponent ], + providers: [ + { provide: ExportService, useValue: exportServiceMock } + ], }) .compileComponents(); }); @@ -80,4 +91,20 @@ describe('StatisticsMapComponent', () => { expect(component.geoChart).toEqual(geoChartExpected); }); + it('should download map as png and jpg', () => { + component.report = report; + fixture.detectChanges(); + component.ngOnInit(); + fixture.detectChanges(); + const downloadPngMapBtn = fixture.debugElement.query(By.css('[data-test="download-png-map-btn"]')); + downloadPngMapBtn.triggerEventHandler('click', null); + fixture.detectChanges(); + const node = fixture.debugElement.query(By.css('[data-test="google-chart-ref"]')).nativeElement; + expect(exportServiceMock.exportAsImage).toHaveBeenCalledWith(node, ExportImageType.png, report.reportType, component.isLoading); + + const downloadJpgMapBtn = fixture.debugElement.query(By.css('[data-test="download-jpg-map-btn"]')); + downloadJpgMapBtn.triggerEventHandler('click', null); + fixture.detectChanges(); + expect(exportServiceMock.exportAsImage).toHaveBeenCalledWith(node, ExportImageType.jpeg, report.reportType, component.isSecondLoading); + }); }); diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts index 4bd4bf8a7b5..6f0eead5f1b 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts @@ -1,6 +1,8 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { UsageReport } from '../../../core/statistics/models/usage-report.model'; import { GoogleChartInterface } from 'ng2-google-charts'; +import { ExportImageType, ExportService } from '../../../core/export-service/export.service'; +import { BehaviorSubject } from 'rxjs'; @Component({ @@ -30,6 +32,23 @@ export class StatisticsMapComponent implements OnInit { * Chart Columns needed to be shown in the tooltip */ chartColumns = []; + /** + * Loading utilized for export functions to disable buttons + */ + isLoading: BehaviorSubject = new BehaviorSubject(false); + /** + * Loading utilized for export functions to disable buttons + */ + isSecondLoading: BehaviorSubject = new BehaviorSubject(false); + /** + * Chart ElementRef + */ + @ViewChild('googleChartRef') googleChartRef: ElementRef; + + constructor( + private exportService: ExportService + ) { + } ngOnInit(): void { if ( !!this.report && !!this.report.points && this.report.points.length > 0 ) { @@ -64,6 +83,22 @@ export class StatisticsMapComponent implements OnInit { } + /** + * Download map as image in png version. + */ + downloadPng() { + this.isLoading.next(false); + const node = this.googleChartRef.nativeElement; + this.exportService.exportAsImage(node, ExportImageType.png, this.report.reportType, this.isLoading); + } + /** + * Download map as image in jpeg version. + */ + downloadJpeg() { + this.isSecondLoading.next(false); + const node = this.googleChartRef.nativeElement; + this.exportService.exportAsImage(node, ExportImageType.jpeg, this.report.reportType, this.isSecondLoading); + } } diff --git a/src/app/submission/import-external/submission-import-external.component.spec.ts b/src/app/submission/import-external/submission-import-external.component.spec.ts index dc53b2e45f3..dfcb85ee347 100644 --- a/src/app/submission/import-external/submission-import-external.component.spec.ts +++ b/src/app/submission/import-external/submission-import-external.component.spec.ts @@ -165,7 +165,7 @@ describe('SubmissionImportExternalComponent test suite', () => { ngbModal.open.and.returnValue({componentInstance: { externalSourceEntry: null}}); comp.import(entry); - expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg' }); + expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg', scrollable: true }); expect(comp.modalRef.componentInstance.externalSourceEntry).toEqual(entry); }); From e96a9794894e5859980b71516b6399eb908e898b Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 12 Sep 2022 20:29:49 +0200 Subject: [PATCH 011/167] [CST-6317] New COLLECTIONS rendering --- .../cris-layout-collection-box.component.html | 8 +++ .../cris-layout-collection-box.component.scss | 0 ...is-layout-collection-box.component.spec.ts | 62 +++++++++++++++++++ .../cris-layout-collection-box.component.ts | 51 +++++++++++++++ src/app/cris-layout/cris-layout.module.ts | 2 + src/app/cris-layout/enums/layout-box.enum.ts | 1 + src/assets/i18n/en.json5 | 2 + 7 files changed, 126 insertions(+) create mode 100644 src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html create mode 100644 src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.scss create mode 100644 src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts create mode 100644 src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html new file mode 100644 index 00000000000..dafa3cecfd3 --- /dev/null +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html @@ -0,0 +1,8 @@ +
+
+
{{ 'cris-layout.rendering.collections.owning-collection.label' | translate }}
+ +
+
diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.scss b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts new file mode 100644 index 00000000000..d6fde55ac6c --- /dev/null +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts @@ -0,0 +1,62 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CrisLayoutCollectionBoxComponent } from './cris-layout-collection-box.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { CrisLayoutBox } from '../../../../../core/layout/models/box.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; +import { Collection } from '../../../../../core/shared/collection.model'; + +describe('CrisLayoutCollectionBoxComponent', () => { + let component: CrisLayoutCollectionBoxComponent; + let fixture: ComponentFixture; + + const testBox = Object.assign(new CrisLayoutBox(), { + 'id': 1, + 'shortname': 'collections', + 'header': 'Collections', + 'entityType': 'Publication', + 'collapsed': false, + 'minor': false, + 'style': null, + 'security': 0, + 'boxType': 'COLLECTIONS', + 'maxColumns': null, + 'configuration': null, + 'metadataSecurityFields': [], + 'container': false + }); + + const owningCollection = Object.assign(new Collection(), {uuid: 'test-collection-uuid'}); + + const owningCollection$ = createSuccessfulRemoteDataObject$(owningCollection); + + const testItem = Object.assign(new Item(), { + type: 'item', + owningCollection: owningCollection$, + uuid: 'test-item-uuid', + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [CrisLayoutCollectionBoxComponent], + providers: [ + { provide: 'boxProvider', useValue: testBox }, + { provide: 'itemProvider', useValue: testItem }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CrisLayoutCollectionBoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts new file mode 100644 index 00000000000..0ae49bc2851 --- /dev/null +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -0,0 +1,51 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { CrisLayoutBoxModelComponent } from '../../../../models/cris-layout-box-component.model'; +import { CrisLayoutBox } from '../../../../../core/layout/models/box.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { TranslateService } from '@ngx-translate/core'; +import { RenderCrisLayoutBoxFor } from '../../../../decorators/cris-layout-box.decorator'; +import { LayoutBox } from '../../../../enums/layout-box.enum'; +import { Observable } from 'rxjs'; +import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; +import { map, shareReplay } from 'rxjs/operators'; + +@Component({ + selector: 'ds-cris-layout-collection-box', + templateUrl: './cris-layout-collection-box.component.html', + styleUrls: ['./cris-layout-collection-box.component.scss'] +}) +@RenderCrisLayoutBoxFor(LayoutBox.COLLECTIONS) +export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponent implements OnInit { + + owningCollectionName$: Observable; + + owningCollectionId$: Observable; + + constructor( + protected translateService: TranslateService, + @Inject('boxProvider') public boxProvider: CrisLayoutBox, + @Inject('itemProvider') public itemProvider: Item + ) { + super(translateService, boxProvider, itemProvider); + } + + ngOnInit(): void { + super.ngOnInit(); + + const collection$ = this.item.owningCollection.pipe( + getFirstSucceededRemoteDataPayload(), + shareReplay(), + ); + + this.owningCollectionName$ = collection$.pipe( + map((coll) => coll.firstMetadataValue('dc.title')), + ); + + this.owningCollectionId$ = collection$.pipe( + map((coll) => coll.uuid), + ); + + } + + +} diff --git a/src/app/cris-layout/cris-layout.module.ts b/src/app/cris-layout/cris-layout.module.ts index a5070c6efb8..91a9c274f7c 100644 --- a/src/app/cris-layout/cris-layout.module.ts +++ b/src/app/cris-layout/cris-layout.module.ts @@ -48,12 +48,14 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; import { SearchModule } from '../shared/search/search.module'; import { AdvancedAttachmentComponent } from './cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/advanced-attachment.component'; import { FileDownloadButtonComponent } from '../shared/file-download-button/file-download-button.component'; +import { CrisLayoutCollectionBoxComponent } from './cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator CrisLayoutVerticalComponent, CrisLayoutHorizontalComponent, CrisLayoutMetadataBoxComponent, + CrisLayoutCollectionBoxComponent, TextComponent, HeadingComponent, CrisLayoutRelationBoxComponent, diff --git a/src/app/cris-layout/enums/layout-box.enum.ts b/src/app/cris-layout/enums/layout-box.enum.ts index df5673b98cc..143c14ab535 100644 --- a/src/app/cris-layout/enums/layout-box.enum.ts +++ b/src/app/cris-layout/enums/layout-box.enum.ts @@ -12,4 +12,5 @@ export enum LayoutBox { ORCID_AUTHORIZATIONS = 'ORCID_AUTHORIZATIONS', ORCID_SYNC_QUEUE = 'ORCID_SYNC_QUEUE', IIIFVIEWER = 'IIIFVIEWER', + COLLECTIONS = 'COLLECTIONS', } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 79ae633c815..ca43ee5bfd3 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1622,6 +1622,8 @@ "cris-layout.attachment.viewMore": "View More", + "cris-layout.rendering.collections.owning-collection.label": "Owning collection", + "curation-task.task.checklinks.label": "Check Links in Metadata", "curation-task.task.noop.label": "NOOP", From 8d2636455fb59e1c62842383e6c42f9e0da3df17 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Wed, 14 Sep 2022 18:22:04 +0530 Subject: [PATCH 012/167] [DSC-740] New COLLECTIONS rendering type to show item's collection with CRIS layout --- .../cris-layout-collection-box.component.html | 33 ++++-- ...is-layout-collection-box.component.spec.ts | 89 ++++++++++++++ .../cris-layout-collection-box.component.ts | 112 +++++++++++++++--- src/assets/i18n/en.json5 | 6 + src/config/default-app-config.ts | 4 + src/config/layout-config.interfaces.ts | 6 + src/environments/environment.test.ts | 4 + 7 files changed, 233 insertions(+), 21 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html index dafa3cecfd3..9ec7d7007b7 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html @@ -1,8 +1,27 @@ -
-
-
{{ 'cris-layout.rendering.collections.owning-collection.label' | translate }}
-
- {{ owningCollectionName$ | async }} +
+
+
{{ 'cris-layout.rendering.collections.owning-collection.label' | translate }}
+
-
-
+
+
{{ 'cris-layout.rendering.collections.mapped-collection.label' | translate }}
+ +
+
\ No newline at end of file diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts index d6fde55ac6c..bf0c80e19d0 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.spec.ts @@ -6,11 +6,21 @@ import { CrisLayoutBox } from '../../../../../core/layout/models/box.model'; import { Item } from '../../../../../core/shared/item.model'; import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { Collection } from '../../../../../core/shared/collection.model'; +import { CollectionDataService } from '../../../../../core/data/collection-data.service'; +import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { By } from '@angular/platform-browser'; +import { of as observableOf } from 'rxjs'; describe('CrisLayoutCollectionBoxComponent', () => { let component: CrisLayoutCollectionBoxComponent; let fixture: ComponentFixture; + const createMockCollection = (id: string) => Object.assign(new Collection(), { + id: id, + name: `collection-${id}`, + }); + const testBox = Object.assign(new CrisLayoutBox(), { 'id': 1, 'shortname': 'collections', @@ -26,6 +36,9 @@ describe('CrisLayoutCollectionBoxComponent', () => { 'metadataSecurityFields': [], 'container': false }); + let collectionDataService; + let mockCollection1: Collection; + let mockPage1: PaginatedList; const owningCollection = Object.assign(new Collection(), {uuid: 'test-collection-uuid'}); @@ -37,7 +50,14 @@ describe('CrisLayoutCollectionBoxComponent', () => { uuid: 'test-item-uuid', }); + mockCollection1 = createMockCollection('c1'); + beforeEach(async () => { + collectionDataService = jasmine.createSpyObj([ + 'findOwningCollectionFor', + 'findMappedCollectionsFor', + ]); + await TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot(), @@ -46,6 +66,7 @@ describe('CrisLayoutCollectionBoxComponent', () => { providers: [ { provide: 'boxProvider', useValue: testBox }, { provide: 'itemProvider', useValue: testItem }, + { provide: CollectionDataService, useValue: collectionDataService }, ], }).compileComponents(); }); @@ -53,10 +74,78 @@ describe('CrisLayoutCollectionBoxComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CrisLayoutCollectionBoxComponent); component = fixture.componentInstance; + mockPage1 = buildPaginatedList(Object.assign(new PageInfo(), { + currentPage: 1, + elementsPerPage: 2, + totalPages: 0, + totalElements: 0, + }), []); + collectionDataService.findOwningCollectionFor.and.returnValue(createSuccessfulRemoteDataObject$(mockCollection1)); + collectionDataService.findMappedCollectionsFor.and.returnValue(createSuccessfulRemoteDataObject$(mockPage1)); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render container', () => { + expect(fixture.debugElement.query(By.css('.container'))).not.toBeNull(); + }); + + describe('without collections', () => { + beforeEach(() => { + component.owningCollection$ = observableOf(null); + fixture.detectChanges(); + }); + + it('should not render container', () => { + expect(fixture.debugElement.query(By.css('.container'))).toBeNull(); + }); + }); + + describe('without owning collections', () => { + beforeEach(() => { + component.owningCollection$ = observableOf(null); + fixture.detectChanges(); + }); + + it('should not render owning collection row', () => { + expect(fixture.debugElement.query(By.css('div[data-test="owningCollection"]'))).toBeNull(); + }); + }); + + describe('with owning collections', () => { + beforeEach(() => { + component.owningCollection$ = observableOf(mockCollection1); + fixture.detectChanges(); + }); + + it('should render owning collection row', () => { + expect(fixture.debugElement.query(By.css('div[data-test="owningCollection"]'))).not.toBeNull(); + }); + }); + + describe('without mapped collections', () => { + beforeEach(() => { + component.mappedCollections$ = observableOf([]); + fixture.detectChanges(); + }); + + it('should not render mapped collections row', () => { + expect(fixture.debugElement.query(By.css('div[data-test="mappedCollections"]'))).toBeNull(); + }); + }); + + describe('with mapped collections', () => { + beforeEach(() => { + component.mappedCollections$ = observableOf([mockCollection1]); + fixture.detectChanges(); + }); + + it('should render mapped collection row', () => { + expect(fixture.debugElement.query(By.css('div[data-test="mappedCollections"]'))).not.toBeNull(); + }); + }); + }); diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts index 0ae49bc2851..9389d9659e5 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -5,9 +5,14 @@ import { Item } from '../../../../../core/shared/item.model'; import { TranslateService } from '@ngx-translate/core'; import { RenderCrisLayoutBoxFor } from '../../../../decorators/cris-layout-box.decorator'; import { LayoutBox } from '../../../../enums/layout-box.enum'; -import { Observable } from 'rxjs'; -import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; -import { map, shareReplay } from 'rxjs/operators'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { getFirstSucceededRemoteDataPayload, getAllCompletedRemoteData, getAllSucceededRemoteDataPayload, getPaginatedListPayload } from '../../../../../core/shared/operators'; +import { startWith, tap, withLatestFrom, switchMap, scan } from 'rxjs/operators'; +import { Collection } from '../../../../../core/shared/collection.model'; +import { CollectionDataService } from '../../../../../core/data/collection-data.service'; +import { FindListOptions } from '../../../../../core/data/request.models'; +import { PaginatedList } from '../../../../../core/data/paginated-list.model'; +import { environment } from '../../../../../../environments/environment'; @Component({ selector: 'ds-cris-layout-collection-box', @@ -17,14 +22,50 @@ import { map, shareReplay } from 'rxjs/operators'; @RenderCrisLayoutBoxFor(LayoutBox.COLLECTIONS) export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponent implements OnInit { - owningCollectionName$: Observable; + separator = '
'; - owningCollectionId$: Observable; + /** + * Amount of mapped collections that should be fetched at once. + */ + pageSize = 5; + + /** + * Last page of the mapped collections that has been fetched. + */ + lastPage$: BehaviorSubject = new BehaviorSubject(0); + + /** + * Push an event to this observable to fetch the next page of mapped collections. + * Because this observable is a behavior subject, the first page will be requested + * immediately after subscription. + */ + loadMore$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * Whether or not a page of mapped collections is currently being loaded. + */ + isLoading$: BehaviorSubject = new BehaviorSubject(true); + + /** + * Whether or not more pages of mapped collections are available. + */ + hasMore$: BehaviorSubject = new BehaviorSubject(true); + + /** + * This includes the owning collection + */ + owningCollection$: Observable; + + /** + * This includes the mapped collection + */ + mappedCollections$: Observable; constructor( protected translateService: TranslateService, @Inject('boxProvider') public boxProvider: CrisLayoutBox, - @Inject('itemProvider') public itemProvider: Item + @Inject('itemProvider') public itemProvider: Item, + private cds: CollectionDataService ) { super(translateService, boxProvider, itemProvider); } @@ -32,19 +73,62 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen ngOnInit(): void { super.ngOnInit(); - const collection$ = this.item.owningCollection.pipe( + this.owningCollection$ = this.cds.findOwningCollectionFor(this.item).pipe( getFirstSucceededRemoteDataPayload(), - shareReplay(), + startWith(null as Collection), ); - this.owningCollectionName$ = collection$.pipe( - map((coll) => coll.firstMetadataValue('dc.title')), - ); + this.mappedCollections$ = this.loadMore$.pipe( + // update isLoading$ + tap(() => this.isLoading$.next(true)), - this.owningCollectionId$ = collection$.pipe( - map((coll) => coll.uuid), - ); + // request next batch of mapped collections + withLatestFrom(this.lastPage$), + switchMap(([_, lastPage]: [void, number]) => { + return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { + elementsPerPage: this.pageSize, + currentPage: lastPage + 1, + })); + }), + + getAllCompletedRemoteData>(), + + // update isLoading$ + tap(() => this.isLoading$.next(false)), + + getAllSucceededRemoteDataPayload(), + + // update hasMore$ + tap((response: PaginatedList) => this.hasMore$.next(response.currentPage < response.totalPages)), + + // update lastPage$ + tap((response: PaginatedList) => this.lastPage$.next(response.currentPage)), + + getPaginatedListPayload(), + + // add current batch to list of collections + scan((prev: Collection[], current: Collection[]) => [...prev, ...current], []), + + startWith([]), + ) as Observable; + } + + handleLoadMore() { + this.loadMore$.next(); + } + + /** + * Returns a string representing the style of field label if exists + */ + get labelStyle(): string { + return environment.crisLayout.collectionsBox.defaultCollectionsLabelColStyle; + } + /** + * Returns a string representing the style of field value if exists + */ + get valueStyle(): string { + return environment.crisLayout.collectionsBox.defaultCollectionsValueColStyle; } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ca43ee5bfd3..e5503cc0dbc 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1624,6 +1624,12 @@ "cris-layout.rendering.collections.owning-collection.label": "Owning collection", + "cris-layout.rendering.collections.mapped-collection.label": "Mapped collection", + + "cris-layout.rendering.collections.loading": "Loading...", + + "cris-layout.rendering.collections.load-more": "Load more", + "curation-task.task.checklinks.label": "Check Links in Metadata", "curation-task.task.noop.label": "NOOP", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 6594cebecd3..fc0d21461c3 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -415,6 +415,10 @@ export class DefaultAppConfig implements AppConfig { metadataBox: { defaultMetadataLabelColStyle: 'col-3', defaultMetadataValueColStyle: 'col-9' + }, + collectionsBox: { + defaultCollectionsLabelColStyle: 'col-3 font-weight-bold', + defaultCollectionsValueColStyle: 'col-9' } }; diff --git a/src/config/layout-config.interfaces.ts b/src/config/layout-config.interfaces.ts index 0b15a06aa92..cdc2f43015b 100644 --- a/src/config/layout-config.interfaces.ts +++ b/src/config/layout-config.interfaces.ts @@ -15,6 +15,11 @@ export interface CrisLayoutMetadataBoxConfig extends Config { defaultMetadataValueColStyle: string; } +export interface CrisLayoutCollectionsBoxConfig extends Config { + defaultCollectionsLabelColStyle: string; + defaultCollectionsValueColStyle: string; +} + export interface CrisLayoutTypeConfig { orientation: string; } @@ -34,6 +39,7 @@ export interface CrisLayoutConfig extends Config { crisRef: CrisRefConfig[]; itemPage: CrisItemPageConfig; metadataBox: CrisLayoutMetadataBoxConfig; + collectionsBox: CrisLayoutCollectionsBoxConfig; } export interface LayoutConfig extends Config { diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index a7873fda79d..169f16fbfc9 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -288,6 +288,10 @@ export const environment: BuildConfig = { metadataBox: { defaultMetadataLabelColStyle: 'col-3', defaultMetadataValueColStyle: 'col-9' + }, + collectionsBox: { + defaultCollectionsLabelColStyle: 'col-3 font-weight-bold', + defaultCollectionsValueColStyle: 'col-9' } }, layout: { From 75c3336595a3ea2b864e0dc033762ffd48f8d0ab Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 15 Sep 2022 19:57:30 +0530 Subject: [PATCH 013/167] [DSC-740] design and functionality fixes --- .../cris-layout-collection-box.component.html | 18 ++--- .../cris-layout-collection-box.component.ts | 68 +++++++++---------- src/assets/i18n/en.json5 | 2 +- src/config/default-app-config.ts | 3 +- src/config/layout-config.interfaces.ts | 2 + src/environments/environment.test.ts | 3 +- 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html index 9ec7d7007b7..38f71cfda7c 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html @@ -5,22 +5,24 @@ {{ (owningCollection$ | async).firstMetadataValue('dc.title') }}
-
+
{{ 'cris-layout.rendering.collections.mapped-collection.label' | translate }}
- diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts index 9389d9659e5..cf9eddfb112 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -5,9 +5,9 @@ import { Item } from '../../../../../core/shared/item.model'; import { TranslateService } from '@ngx-translate/core'; import { RenderCrisLayoutBoxFor } from '../../../../decorators/cris-layout-box.decorator'; import { LayoutBox } from '../../../../enums/layout-box.enum'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, of } from 'rxjs'; import { getFirstSucceededRemoteDataPayload, getAllCompletedRemoteData, getAllSucceededRemoteDataPayload, getPaginatedListPayload } from '../../../../../core/shared/operators'; -import { startWith, tap, withLatestFrom, switchMap, scan } from 'rxjs/operators'; +import { shareReplay, tap, switchMap, scan, map } from 'rxjs/operators'; import { Collection } from '../../../../../core/shared/collection.model'; import { CollectionDataService } from '../../../../../core/data/collection-data.service'; import { FindListOptions } from '../../../../../core/data/request.models'; @@ -22,7 +22,7 @@ import { environment } from '../../../../../../environments/environment'; @RenderCrisLayoutBoxFor(LayoutBox.COLLECTIONS) export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponent implements OnInit { - separator = '
'; + isInline = environment.crisLayout.collectionsBox.isInline; /** * Amount of mapped collections that should be fetched at once. @@ -32,14 +32,7 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen /** * Last page of the mapped collections that has been fetched. */ - lastPage$: BehaviorSubject = new BehaviorSubject(0); - - /** - * Push an event to this observable to fetch the next page of mapped collections. - * Because this observable is a behavior subject, the first page will be requested - * immediately after subscription. - */ - loadMore$: BehaviorSubject = new BehaviorSubject(undefined); + lastPage = 0; /** * Whether or not a page of mapped collections is currently being loaded. @@ -73,24 +66,21 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen ngOnInit(): void { super.ngOnInit(); - this.owningCollection$ = this.cds.findOwningCollectionFor(this.item).pipe( + this.owningCollection$ = this.item.owningCollection.pipe( getFirstSucceededRemoteDataPayload(), - startWith(null as Collection), + shareReplay(), ); - this.mappedCollections$ = this.loadMore$.pipe( - // update isLoading$ - tap(() => this.isLoading$.next(true)), - - // request next batch of mapped collections - withLatestFrom(this.lastPage$), - switchMap(([_, lastPage]: [void, number]) => { - return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { - elementsPerPage: this.pageSize, - currentPage: lastPage + 1, - })); - }), + this.handleLoadMore(); + } + handleLoadMore() { + this.isLoading$.next(true); + const oldMappedCollections$ = this.mappedCollections$; + this.mappedCollections$ = this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { + elementsPerPage: this.pageSize, + currentPage: this.lastPage + 1, + })).pipe( getAllCompletedRemoteData>(), // update isLoading$ @@ -99,22 +89,26 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen getAllSucceededRemoteDataPayload(), // update hasMore$ - tap((response: PaginatedList) => this.hasMore$.next(response.currentPage < response.totalPages)), + tap((response: PaginatedList) => this.hasMore$.next(this.lastPage < response.totalPages)), - // update lastPage$ - tap((response: PaginatedList) => this.lastPage$.next(response.currentPage)), + // update lastPage + tap((response: PaginatedList) => this.lastPage = response.currentPage), getPaginatedListPayload(), // add current batch to list of collections scan((prev: Collection[], current: Collection[]) => [...prev, ...current], []), - startWith([]), - ) as Observable; - } - - handleLoadMore() { - this.loadMore$.next(); + switchMap((collections: Collection[]) => { + if (oldMappedCollections$) { + return oldMappedCollections$.pipe( + map((mappedCollection) => [...mappedCollection, ...collections]) + ); + } else { + return of(collections); + } + }) + ); } /** @@ -131,5 +125,11 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen return environment.crisLayout.collectionsBox.defaultCollectionsValueColStyle; } + /** + * Returns a string representing the style of row if exists + */ + get rowStyle(): string { + return environment.crisLayout.collectionsBox.defaultCollectionsRowStyle; + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e5503cc0dbc..ee207485400 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1624,7 +1624,7 @@ "cris-layout.rendering.collections.owning-collection.label": "Owning collection", - "cris-layout.rendering.collections.mapped-collection.label": "Mapped collection", + "cris-layout.rendering.collections.mapped-collection.label": "Mapped collections", "cris-layout.rendering.collections.loading": "Loading...", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index fc0d21461c3..3f782fa1990 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -418,7 +418,8 @@ export class DefaultAppConfig implements AppConfig { }, collectionsBox: { defaultCollectionsLabelColStyle: 'col-3 font-weight-bold', - defaultCollectionsValueColStyle: 'col-9' + defaultCollectionsValueColStyle: 'col-9', + isInline: true } }; diff --git a/src/config/layout-config.interfaces.ts b/src/config/layout-config.interfaces.ts index cdc2f43015b..5c91ca190aa 100644 --- a/src/config/layout-config.interfaces.ts +++ b/src/config/layout-config.interfaces.ts @@ -18,6 +18,8 @@ export interface CrisLayoutMetadataBoxConfig extends Config { export interface CrisLayoutCollectionsBoxConfig extends Config { defaultCollectionsLabelColStyle: string; defaultCollectionsValueColStyle: string; + isInline: boolean; + defaultCollectionsRowStyle?: string; } export interface CrisLayoutTypeConfig { diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 169f16fbfc9..fa194c12989 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -291,7 +291,8 @@ export const environment: BuildConfig = { }, collectionsBox: { defaultCollectionsLabelColStyle: 'col-3 font-weight-bold', - defaultCollectionsValueColStyle: 'col-9' + defaultCollectionsValueColStyle: 'col-9', + isInline: true } }, layout: { From 48a5e6bb7de2e1435d6daa5239a8e50b05b6dc0e Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 16 Sep 2022 15:44:21 +0200 Subject: [PATCH 014/167] [DSC-740] Graphical fixes --- .../cris-layout-collection-box.component.html | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html index 38f71cfda7c..e1126d358d7 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.html @@ -1,29 +1,31 @@ -
-
-
{{ 'cris-layout.rendering.collections.owning-collection.label' | translate }}
- +
+
+
{{ 'cris-layout.rendering.collections.owning-collection.label' | translate }}
+ -
-
{{ 'cris-layout.rendering.collections.mapped-collection.label' | translate }}
- +
+
+
{{ 'cris-layout.rendering.collections.mapped-collection.label' | translate }}
+
+ +
+ {{'cris-layout.rendering.collections.loading' | translate}} +
+ + + {{'cris-layout.rendering.collections.load-more' | translate}} +
-
\ No newline at end of file +
+
From 7cceda1f8173ad6097ab914a9ad123abf1a7c7f3 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 16 Sep 2022 16:18:50 +0200 Subject: [PATCH 015/167] [DSC-740] Refactor WIP --- .../cris-layout-collection-box.component.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts index cf9eddfb112..0fb92ea60c2 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -13,6 +13,7 @@ import { CollectionDataService } from '../../../../../core/data/collection-data. import { FindListOptions } from '../../../../../core/data/request.models'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { environment } from '../../../../../../environments/environment'; +import { RemoteData } from '../../../../../core/data/remote-data'; @Component({ selector: 'ds-cris-layout-collection-box', @@ -77,10 +78,7 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen handleLoadMore() { this.isLoading$.next(true); const oldMappedCollections$ = this.mappedCollections$; - this.mappedCollections$ = this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { - elementsPerPage: this.pageSize, - currentPage: this.lastPage + 1, - })).pipe( + this.mappedCollections$ = this.mappedCollectionPage(this.lastPage).pipe( getAllCompletedRemoteData>(), // update isLoading$ @@ -111,6 +109,13 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen ); } + mappedCollectionPage(page: number): Observable>> { + return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { + elementsPerPage: this.pageSize, + currentPage: page, + })); + } + /** * Returns a string representing the style of field label if exists */ From 70ad345701069b0f1deeed752e8de454b3e1ae8d Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 16 Sep 2022 16:34:23 +0200 Subject: [PATCH 016/167] [DSC-740] Refactor WIP --- .../cris-layout-collection-box.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts index 0fb92ea60c2..0811360d4fa 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -78,7 +78,7 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen handleLoadMore() { this.isLoading$.next(true); const oldMappedCollections$ = this.mappedCollections$; - this.mappedCollections$ = this.mappedCollectionPage(this.lastPage).pipe( + this.mappedCollections$ = this.loadMappedCollectionPage(this.lastPage).pipe( getAllCompletedRemoteData>(), // update isLoading$ @@ -109,11 +109,13 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen ); } - mappedCollectionPage(page: number): Observable>> { + loadMappedCollectionPage(page: number): Observable>> { return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { elementsPerPage: this.pageSize, currentPage: page, - })); + })).pipe( + + ); } /** From 6ec7f63f31200590658ab03a0bf3d5180b4530c7 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Mon, 19 Sep 2022 20:53:26 +0530 Subject: [PATCH 017/167] [DSC-740] load more mapped collection optimization --- .../cris-layout-collection-box.component.ts | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts index 0811360d4fa..1f5a2445d9f 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/cris-layout-collection-box/cris-layout-collection-box.component.ts @@ -5,15 +5,16 @@ import { Item } from '../../../../../core/shared/item.model'; import { TranslateService } from '@ngx-translate/core'; import { RenderCrisLayoutBoxFor } from '../../../../decorators/cris-layout-box.decorator'; import { LayoutBox } from '../../../../enums/layout-box.enum'; -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { getFirstSucceededRemoteDataPayload, getAllCompletedRemoteData, getAllSucceededRemoteDataPayload, getPaginatedListPayload } from '../../../../../core/shared/operators'; -import { shareReplay, tap, switchMap, scan, map } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; +import { getFirstSucceededRemoteDataPayload, getPaginatedListPayload, getFirstCompletedRemoteData } from '../../../../../core/shared/operators'; +import { shareReplay, tap, map } from 'rxjs/operators'; import { Collection } from '../../../../../core/shared/collection.model'; import { CollectionDataService } from '../../../../../core/data/collection-data.service'; import { FindListOptions } from '../../../../../core/data/request.models'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { environment } from '../../../../../../environments/environment'; import { RemoteData } from '../../../../../core/data/remote-data'; +import { hasValue } from 'src/app/shared/empty.util'; @Component({ selector: 'ds-cris-layout-collection-box', @@ -53,7 +54,7 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen /** * This includes the mapped collection */ - mappedCollections$: Observable; + mappedCollections$: Observable = of([]); constructor( protected translateService: TranslateService, @@ -77,44 +78,33 @@ export class CrisLayoutCollectionBoxComponent extends CrisLayoutBoxModelComponen handleLoadMore() { this.isLoading$.next(true); - const oldMappedCollections$ = this.mappedCollections$; - this.mappedCollections$ = this.loadMappedCollectionPage(this.lastPage).pipe( - getAllCompletedRemoteData>(), + const newMappedCollections$ = this.loadMappedCollectionPage(); + this.mappedCollections$ = combineLatest([this.mappedCollections$, newMappedCollections$]).pipe( + map(([mappedCollections, newMappedCollections]: [Collection[], Collection[]]) => { + return [...mappedCollections, ...newMappedCollections].filter(collection => hasValue(collection)); + }), + ); + } + + loadMappedCollectionPage(): Observable { + return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { + elementsPerPage: this.pageSize, + currentPage: this.lastPage + 1, + })).pipe( + getFirstCompletedRemoteData>(), // update isLoading$ tap(() => this.isLoading$.next(false)), - getAllSucceededRemoteDataPayload(), - - // update hasMore$ - tap((response: PaginatedList) => this.hasMore$.next(this.lastPage < response.totalPages)), + getFirstSucceededRemoteDataPayload(), // update lastPage tap((response: PaginatedList) => this.lastPage = response.currentPage), - getPaginatedListPayload(), - - // add current batch to list of collections - scan((prev: Collection[], current: Collection[]) => [...prev, ...current], []), - - switchMap((collections: Collection[]) => { - if (oldMappedCollections$) { - return oldMappedCollections$.pipe( - map((mappedCollection) => [...mappedCollection, ...collections]) - ); - } else { - return of(collections); - } - }) - ); - } - - loadMappedCollectionPage(page: number): Observable>> { - return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), { - elementsPerPage: this.pageSize, - currentPage: page, - })).pipe( + // update hasMore$ + tap((response: PaginatedList) => this.hasMore$.next(this.lastPage < response.totalPages)), + getPaginatedListPayload(), ); } From 3deb5f6eb7e00b9d4d66e3096c3304a5c3d26662 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 10 Nov 2022 20:53:56 +0530 Subject: [PATCH 018/167] [DSC-818] Add bulk export page and link it to side menu --- .../data/processes/script-data.service.ts | 1 + src/app/menu.resolver.ts | 17 ++ .../export-collection-menu.component.ts | 4 +- .../dso-selector-modal-wrapper.component.ts | 1 + .../export-excel-selector.component.ts | 4 +- ...rt-metadata-xls-selector.component.spec.ts | 180 ++++++++++++++++++ .../export-metadata-xls-selector.component.ts | 105 ++++++++++ src/app/shared/shared.module.ts | 3 + src/assets/i18n/en.json5 | 14 +- 9 files changed, 324 insertions(+), 5 deletions(-) create mode 100644 src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.ts diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index fb6dc813084..ce4f9196aab 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -25,6 +25,7 @@ import { CoreState } from '../../core-state.model'; export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import'; export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; +export const COLLECTION_EXPORT_SCRIPT_NAME = 'collection-export'; export const ITEM_EXPORT_SCRIPT_NAME = 'item-export'; export const BULK_ITEM_EXPORT_SCRIPT_NAME = 'bulk-item-export'; export const BATCH_IMPORT_SCRIPT_NAME = 'import'; diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 6c8522ba0b0..fa2e8771b77 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -48,6 +48,9 @@ import { Section } from './core/layout/models/section.model'; import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; +import { + ExportMetadataXlsSelectorComponent +} from './shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component'; /** * Creates all of the app's menus @@ -511,6 +514,20 @@ export class MenuResolver implements Resolve { } as OnClickMenuItemModel, shouldPersistOnRouteChange: true }); + this.menuService.addSection(MenuID.ADMIN, { + id: 'export_metadata_xls', + parentID: 'export', + active: true, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.export_metadata_xls', + function: () => { + this.modalService.open(ExportMetadataXlsSelectorComponent); + } + } as OnClickMenuItemModel, + shouldPersistOnRouteChange: true + }); this.menuService.addSection(MenuID.ADMIN, { id: 'export_batch', parentID: 'export', diff --git a/src/app/shared/context-menu/export-collection/export-collection-menu.component.ts b/src/app/shared/context-menu/export-collection/export-collection-menu.component.ts index e18694417e5..dccc68de169 100644 --- a/src/app/shared/context-menu/export-collection/export-collection-menu.component.ts +++ b/src/app/shared/context-menu/export-collection/export-collection-menu.component.ts @@ -9,7 +9,7 @@ import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model' import { ContextMenuEntryComponent } from '../context-menu-entry.component'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { ProcessParameter } from '../../../process-page/processes/process-parameter.model'; -import { ScriptDataService } from '../../../core/data/processes/script-data.service'; +import { COLLECTION_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../core/data/processes/script-data.service'; import { NotificationsService } from '../../notifications/notifications.service'; import { RequestService } from '../../../core/data/request.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; @@ -62,7 +62,7 @@ export class ExportCollectionMenuComponent extends ContextMenuEntryComponent { { name: '-c', value: this.contextMenuObject.id } ]; - this.scriptService.invoke('collection-export', stringParameters, []) + this.scriptService.invoke(COLLECTION_EXPORT_SCRIPT_NAME, stringParameters, []) .pipe(getFirstCompletedRemoteData()) .subscribe((rd: RemoteData) => { if (rd.isSuccess) { diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 28b4556e2d2..e6a11c40d55 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -10,6 +10,7 @@ export enum SelectorActionType { CREATE = 'create', EDIT = 'edit', EXPORT_METADATA = 'export-metadata', + EXPORT_METADATA_XLS = 'export-metadata-xls', IMPORT_ITEM = 'import-item', EXPORT_ITEM = 'export-item', IMPORT_BATCH = 'import-batch', diff --git a/src/app/shared/dso-selector/modal-wrappers/export-excel-selector/export-excel-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-excel-selector/export-excel-selector.component.ts index 3c7220492ae..d2d58717a2f 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-excel-selector/export-excel-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-excel-selector/export-excel-selector.component.ts @@ -6,7 +6,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; import { TranslateService } from '@ngx-translate/core'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { ScriptDataService } from '../../../../core/data/processes/script-data.service'; +import { COLLECTION_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; import { RemoteData } from '../../../../core/data/remote-data'; import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; import { Process } from '../../../../process-page/processes/process.model'; @@ -45,7 +45,7 @@ export class ExportExcelSelectorComponent extends DSOSelectorModalWrapperCompone { name: '-c', value: dso.id } ]; - this.scriptService.invoke('collection-export', stringParameters, []) + this.scriptService.invoke(COLLECTION_EXPORT_SCRIPT_NAME, stringParameters, []) .pipe(getFirstCompletedRemoteData()) .subscribe((rd: RemoteData) => { if (rd.isSuccess) { diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.spec.ts new file mode 100644 index 00000000000..d8048fd6e5d --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.spec.ts @@ -0,0 +1,180 @@ +import { of as observableOf } from 'rxjs'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { DebugElement, NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { COLLECTION_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { + createFailedRemoteDataObject$, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$ +} from '../../../remote-data.utils'; +import { ExportMetadataXlsSelectorComponent } from './export-metadata-xls-selector.component'; + +// No way to add entryComponents yet to testbed; alternative implemented; source: https://stackoverflow.com/questions/41689468/how-to-shallow-test-a-component-with-an-entrycomponents +@NgModule({ + imports: [NgbModalModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + exports: [], + declarations: [ConfirmationModalComponent], + providers: [] +}) +class ModelTestModule { +} + +describe('ExportMetadataXlsSelectorComponent', () => { + let component: ExportMetadataXlsSelectorComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + let modalRef; + + let router; + let notificationService: NotificationsServiceStub; + let scriptService; + + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' + }); + + const mockCollection: Collection = Object.assign(new Collection(), { + id: 'test-collection-1-1', + uuid: 'test-collection-1-1', + name: 'test-collection-1', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-collection-1' + } + ] + } + }); + + const itemRD = createSuccessfulRemoteDataObject(mockItem); + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + + beforeEach(waitForAsync(() => { + notificationService = new NotificationsServiceStub(); + router = jasmine.createSpyObj('router', { + navigateByUrl: jasmine.createSpy('navigateByUrl') + }); + scriptService = jasmine.createSpyObj('scriptService', + { + invoke: createSuccessfulRemoteDataObject$({ processId: '45' }) + } + ); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ModelTestModule], + declarations: [ExportMetadataXlsSelectorComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + { provide: NotificationsService, useValue: notificationService }, + { provide: ScriptDataService, useValue: scriptService }, + { + provide: ActivatedRoute, + useValue: { + root: { + snapshot: { + data: { + dso: itemRD, + }, + }, + } + }, + }, + { + provide: Router, useValue: router + } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ExportMetadataXlsSelectorComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + const modalService = TestBed.inject(NgbModal); + modalRef = modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.response = observableOf(true); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if item is selected', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + component.navigate(mockItem).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('should not invoke collection-export script', () => { + expect(scriptService.invoke).not.toHaveBeenCalled(); + }); + }); + + describe('if collection is selected', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + spyOn((component as any).modalService, 'open').and.returnValue(modalRef); + component.navigate(mockCollection).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('should invoke the collection-export script with option -c uuid', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '-c', value: mockCollection.uuid }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(COLLECTION_EXPORT_SCRIPT_NAME, parameterValues, []); + }); + it('success notification is shown', () => { + expect(scriptRequestSucceeded).toBeTrue(); + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45'); + }); + }); + + describe('if collection is selected; but script invoke fails', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + spyOn((component as any).modalService, 'open').and.returnValue(modalRef); + jasmine.getEnv().allowRespy(true); + spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + component.navigate(mockCollection).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('error notification is shown', () => { + expect(scriptRequestSucceeded).toBeFalse(); + expect(notificationService.error).toHaveBeenCalled(); + }); + }); + +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.ts new file mode 100644 index 00000000000..9f1a4dfc9ee --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { COLLECTION_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; +import { Collection } from '../../../../core/shared/collection.model'; +import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { isNotEmpty } from '../../../empty.util'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { Process } from '../../../../process-page/processes/process.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths'; + +/** + * Component to wrap a list of existing dso's inside a modal + * Used to choose a dso from to export metadata of + */ +@Component({ + selector: 'ds-export-metadata-xls-selector', + templateUrl: '../dso-selector-modal-wrapper.component.html', +}) +export class ExportMetadataXlsSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { + configuration = 'backend'; + objectType = DSpaceObjectType.DSPACEOBJECT; + selectorTypes = [DSpaceObjectType.COLLECTION]; + action = SelectorActionType.EXPORT_METADATA_XLS; + + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, + protected notificationsService: NotificationsService, protected translationService: TranslateService, + protected scriptDataService: ScriptDataService, + private modalService: NgbModal) { + super(activeModal, route); + } + + /** + * If the dso is a collection: start export-metadata-xls script & navigate to process if successful + * Otherwise show error message + */ + navigate(dso: DSpaceObject): Observable { + if (dso instanceof Collection) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = dso; + modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata-xls.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata-xls.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata-xls.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.export-metadata-xls.confirm'; + modalRef.componentInstance.confirmIcon = 'fas fa-file-export'; + const resp$ = modalRef.componentInstance.response.pipe(switchMap((confirm: boolean) => { + if (confirm) { + const startScriptSucceeded$ = this.startScriptNotifyAndRedirect(dso); + return startScriptSucceeded$.pipe( + switchMap((r: boolean) => { + return observableOf(r); + }) + ); + } else { + const modalRefExport = this.modalService.open(ExportMetadataXlsSelectorComponent); + modalRefExport.componentInstance.dsoRD = createSuccessfulRemoteDataObject(dso); + } + })); + resp$.subscribe(); + return resp$; + } else { + return observableOf(false); + } + } + + /** + * Start export-metadata-xls script of dso & navigate to process if successful + * Otherwise show error message + * @param dso Dso to export + */ + private startScriptNotifyAndRedirect(dso: DSpaceObject): Observable { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '-c', value: dso.uuid }), + ]; + return this.scriptDataService.invoke(COLLECTION_EXPORT_SCRIPT_NAME, parameterValues, []) + .pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translationService.get('process.new.notification.success.title'); + const content = this.translationService.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + return true; + } else { + const title = this.translationService.get('process.new.notification.error.title'); + const content = this.translationService.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + return false; + } + })); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c43d09aa9f3..992bd3cf10a 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -327,6 +327,7 @@ import { ItemCorrectionComponent } from './object-collection/shared/mydspace-ite import { MetricsModule } from './metric/metrics.module'; import { SearchChartBarHorizontalComponent } from './search/search-charts/search-chart/search-chart-bar-horizontal/search-chart-bar-horizontal.component'; import { ThumbnailService } from './thumbnail/thumbnail.service'; +import { ExportMetadataXlsSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-xls-selector/export-metadata-xls-selector.component'; const MODULES = [ CommonModule, @@ -480,6 +481,7 @@ const COMPONENTS = [ CollectionDropdownComponent, EntityDropdownComponent, ExportMetadataSelectorComponent, + ExportMetadataXlsSelectorComponent, ImportBatchSelectorComponent, ExportBatchSelectorComponent, ConfirmationModalComponent, @@ -579,6 +581,7 @@ const ENTRY_COMPONENTS = [ BitstreamRequestACopyPageComponent, CurationFormComponent, ExportMetadataSelectorComponent, + ExportMetadataXlsSelectorComponent, ImportBatchSelectorComponent, ExportBatchSelectorComponent, ConfirmationModalComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b38b4c6a56c..aae6dfecb01 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1762,6 +1762,8 @@ "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.export-metadata-xls.dspaceobject.head": "Export metadata from", + "dso-selector.import-item.item.head": "Import items", "dso-selector.import-item.sub-level": "Bulk import items in", @@ -1802,6 +1804,14 @@ "confirmation-modal.export-metadata.confirm": "Export", + "confirmation-modal.export-metadata-xls.header": "Export metadata for {{ dsoName }}", + + "confirmation-modal.export-metadata-xls.info": "Are you sure you want to export metadata for {{ dsoName }}", + + "confirmation-modal.export-metadata-xls.cancel": "Cancel", + + "confirmation-modal.export-metadata-xls.confirm": "Export", + "confirmation-modal.export-batch.header": "Export batch (ZIP) for {{ dsoName }}", "confirmation-modal.export-batch.info": "Are you sure you want to export batch (ZIP) for {{ dsoName }}", @@ -3471,7 +3481,9 @@ "menu.section.export_item": "Item", - "menu.section.export_metadata": "Metadata", + "menu.section.export_metadata": "Metadata CSV", + + "menu.section.export_metadata_xls": "Metadata XLS", "menu.section.import_from_excel": "Import from excel", From f4e36aa261a4d0245cd3c304de118633de81baf5 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 11 Nov 2022 17:44:20 +0100 Subject: [PATCH 019/167] [DSC-818] renaming export-metadata to export-metadata-csv --- src/app/menu.resolver.ts | 12 ++++++------ .../dso-selector-modal-wrapper.component.ts | 2 +- .../export-batch-selector.component.ts | 2 +- ...port-metadata-csv-selector.component.spec.ts} | 12 ++++++------ .../export-metadata-csv-selector.component.ts} | 16 ++++++++-------- src/app/shared/shared.module.ts | 8 ++++---- src/assets/i18n/en.json5 | 12 ++++++------ 7 files changed, 32 insertions(+), 32 deletions(-) rename src/app/shared/dso-selector/modal-wrappers/{export-metadata-selector/export-metadata-selector.component.spec.ts => export-metadata-csv-selector/export-metadata-csv-selector.component.spec.ts} (94%) rename src/app/shared/dso-selector/modal-wrappers/{export-metadata-selector/export-metadata-selector.component.ts => export-metadata-csv-selector/export-metadata-csv-selector.component.ts} (93%) diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index fa2e8771b77..8604122f300 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -33,8 +33,8 @@ import { EditItemSelectorComponent } from './shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { - ExportMetadataSelectorComponent -} from './shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; + ExportMetadataCsvSelectorComponent +} from './shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component'; import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { @@ -431,7 +431,7 @@ export class MenuResolver implements Resolve { } /** - * Create menu sections dependent on whether or not the current user is a site administrator and on whether or not + * Create menu sections depending on whether or not the current user is a site administrator and on whether or not * the export scripts exist and the current user is allowed to execute them */ createExportMenuSections() { @@ -501,15 +501,15 @@ export class MenuResolver implements Resolve { shouldPersistOnRouteChange: true }); this.menuService.addSection(MenuID.ADMIN, { - id: 'export_metadata', + id: 'export_metadata_csv', parentID: 'export', active: true, visible: true, model: { type: MenuItemType.ONCLICK, - text: 'menu.section.export_metadata', + text: 'menu.section.export_metadata_csv', function: () => { - this.modalService.open(ExportMetadataSelectorComponent); + this.modalService.open(ExportMetadataCsvSelectorComponent); } } as OnClickMenuItemModel, shouldPersistOnRouteChange: true diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index e6a11c40d55..41a448e714b 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -9,7 +9,7 @@ import { hasValue, isNotEmpty } from '../../empty.util'; export enum SelectorActionType { CREATE = 'create', EDIT = 'edit', - EXPORT_METADATA = 'export-metadata', + EXPORT_METADATA_CSV = 'export-metadata-csv', EXPORT_METADATA_XLS = 'export-metadata-xls', IMPORT_ITEM = 'import-item', EXPORT_ITEM = 'export-item', diff --git a/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts index 8a48d8a474d..47c38b6f748 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts @@ -26,7 +26,7 @@ import { FeatureID } from '../../../../core/data/feature-authorization/feature-i * Used to choose a dso from to export metadata of */ @Component({ - selector: 'ds-export-metadata-selector', + selector: 'ds-export-metadata-csv-selector', templateUrl: '../dso-selector-modal-wrapper.component.html', }) export class ExportBatchSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.spec.ts similarity index 94% rename from src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts rename to src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.spec.ts index f6b3581df5b..9344b95f65a 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.spec.ts @@ -19,7 +19,7 @@ import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; -import { ExportMetadataSelectorComponent } from './export-metadata-selector.component'; +import { ExportMetadataCsvSelectorComponent } from './export-metadata-csv-selector.component'; // No way to add entryComponents yet to testbed; alternative implemented; source: https://stackoverflow.com/questions/41689468/how-to-shallow-test-a-component-with-an-entrycomponents @NgModule({ @@ -38,9 +38,9 @@ import { ExportMetadataSelectorComponent } from './export-metadata-selector.comp class ModelTestModule { } -describe('ExportMetadataSelectorComponent', () => { - let component: ExportMetadataSelectorComponent; - let fixture: ComponentFixture; +describe('ExportMetadataCsvSelectorComponent', () => { + let component: ExportMetadataCsvSelectorComponent; + let fixture: ComponentFixture; let debugElement: DebugElement; let modalRef; @@ -97,7 +97,7 @@ describe('ExportMetadataSelectorComponent', () => { ); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ModelTestModule], - declarations: [ExportMetadataSelectorComponent], + declarations: [ExportMetadataCsvSelectorComponent], providers: [ { provide: NgbActiveModal, useValue: modalStub }, { provide: NotificationsService, useValue: notificationService }, @@ -124,7 +124,7 @@ describe('ExportMetadataSelectorComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(ExportMetadataSelectorComponent); + fixture = TestBed.createComponent(ExportMetadataCsvSelectorComponent); component = fixture.componentInstance; debugElement = fixture.debugElement; const modalService = TestBed.inject(NgbModal); diff --git a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.ts similarity index 93% rename from src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts rename to src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.ts index a04fc0a1cd2..a18474a7519 100644 --- a/src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component.ts @@ -25,13 +25,13 @@ import { getProcessDetailRoute } from '../../../../process-page/process-page-rou * Used to choose a dso from to export metadata of */ @Component({ - selector: 'ds-export-metadata-selector', + selector: 'ds-export-metadata-csv-selector', templateUrl: '../dso-selector-modal-wrapper.component.html', }) -export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { +export class ExportMetadataCsvSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { objectType = DSpaceObjectType.DSPACEOBJECT; selectorTypes = [DSpaceObjectType.COLLECTION, DSpaceObjectType.COMMUNITY]; - action = SelectorActionType.EXPORT_METADATA; + action = SelectorActionType.EXPORT_METADATA_CSV; constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, protected notificationsService: NotificationsService, protected translationService: TranslateService, @@ -48,10 +48,10 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp if (dso instanceof Collection || dso instanceof Community) { const modalRef = this.modalService.open(ConfirmationModalComponent); modalRef.componentInstance.dso = dso; - modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata.header'; - modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata.info'; - modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata.cancel'; - modalRef.componentInstance.confirmLabel = 'confirmation-modal.export-metadata.confirm'; + modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata-csv.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata-csv.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata-csv.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.export-metadata-csv.confirm'; modalRef.componentInstance.confirmIcon = 'fas fa-file-export'; const resp$ = modalRef.componentInstance.response.pipe(switchMap((confirm: boolean) => { if (confirm) { @@ -62,7 +62,7 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp }) ); } else { - const modalRefExport = this.modalService.open(ExportMetadataSelectorComponent); + const modalRefExport = this.modalService.open(ExportMetadataCsvSelectorComponent); modalRefExport.componentInstance.dsoRD = createSuccessfulRemoteDataObject(dso); } })); diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 992bd3cf10a..dd463c3f71e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -23,8 +23,8 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MomentModule } from 'ngx-moment'; import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; import { - ExportMetadataSelectorComponent -} from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; + ExportMetadataCsvSelectorComponent +} from './dso-selector/modal-wrappers/export-metadata-csv-selector/export-metadata-csv-selector.component'; import { ExportBatchSelectorComponent } from './dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; @@ -480,7 +480,7 @@ const COMPONENTS = [ BitstreamRequestACopyPageComponent, CollectionDropdownComponent, EntityDropdownComponent, - ExportMetadataSelectorComponent, + ExportMetadataCsvSelectorComponent, ExportMetadataXlsSelectorComponent, ImportBatchSelectorComponent, ExportBatchSelectorComponent, @@ -580,7 +580,7 @@ const ENTRY_COMPONENTS = [ BitstreamDownloadPageComponent, BitstreamRequestACopyPageComponent, CurationFormComponent, - ExportMetadataSelectorComponent, + ExportMetadataCsvSelectorComponent, ExportMetadataXlsSelectorComponent, ImportBatchSelectorComponent, ExportBatchSelectorComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index aae6dfecb01..0c9a87762d7 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1760,7 +1760,7 @@ "dso-selector.error.title": "An error occurred searching for a {{ type }}", - "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.export-metadata-csv.dspaceobject.head": "Export metadata from", "dso-selector.export-metadata-xls.dspaceobject.head": "Export metadata from", @@ -1796,13 +1796,13 @@ "dso-selector.export-item.sub-level": "Export to excel items in", - "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", + "confirmation-modal.export-metadata-csv.header": "Export metadata for {{ dsoName }}", - "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", + "confirmation-modal.export-metadata-csv.info": "Are you sure you want to export metadata for {{ dsoName }}", - "confirmation-modal.export-metadata.cancel": "Cancel", + "confirmation-modal.export-metadata-csv.cancel": "Cancel", - "confirmation-modal.export-metadata.confirm": "Export", + "confirmation-modal.export-metadata-csv.confirm": "Export", "confirmation-modal.export-metadata-xls.header": "Export metadata for {{ dsoName }}", @@ -3481,7 +3481,7 @@ "menu.section.export_item": "Item", - "menu.section.export_metadata": "Metadata CSV", + "menu.section.export_metadata_csv": "Metadata CSV", "menu.section.export_metadata_xls": "Metadata XLS", From 47e65861e1033695c7c34247560ba9bcaccf6d53 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 2 Mar 2023 12:06:41 +0100 Subject: [PATCH 020/167] [DSC-966] Show metric donuts in column --- .../object-list/metric-donuts/metric-donuts.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/object-list/metric-donuts/metric-donuts.component.html b/src/app/shared/object-list/metric-donuts/metric-donuts.component.html index 539c0839236..eb74c7aaedd 100644 --- a/src/app/shared/object-list/metric-donuts/metric-donuts.component.html +++ b/src/app/shared/object-list/metric-donuts/metric-donuts.component.html @@ -1,7 +1,7 @@ -
+
Date: Wed, 10 May 2023 14:18:32 +0200 Subject: [PATCH 021/167] [DSC-1058] show filter labels on filter select --- src/app/shared/search/search.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/search/search.component.html b/src/app/shared/search/search.component.html index 0e7a0dcd21e..be0d7b56914 100644 --- a/src/app/shared/search/search.component.html +++ b/src/app/shared/search/search.component.html @@ -117,7 +117,7 @@
- +
From 0f0c27d054e163989c3b0d2c6fdaf7a0a5cb1f36 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 30 May 2023 16:47:20 +0200 Subject: [PATCH 022/167] [DSC-38] Export statistics map --- package.json | 4 +- src/app/core/export-service/export.service.ts | 17 ++++++ .../statistics-map.component.html | 44 ++++++++++----- .../statistics-map.component.ts | 45 +++++++--------- src/assets/i18n/en.json5 | 4 +- yarn.lock | 53 ++++++++++--------- 6 files changed, 100 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 399441566af..239bd948a85 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "morgan": "^1.10.0", "ng-mocks": "^13.1.1", "ng2-file-upload": "1.4.0", - "ng2-google-charts": "^6.1.0", + "ng2-google-charts": "^7.0.0", "ng2-nouislider": "^1.8.3", "ngx-infinite-scroll": "^10.0.1", "ngx-moment": "^5.0.0", @@ -181,7 +181,7 @@ "eslint": "^8.2.0", "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jsdoc": "^38.0.6", + "eslint-plugin-jsdoc": "^39.6.4", "eslint-plugin-unused-imports": "^2.0.0", "fork-ts-checker-webpack-plugin": "^6.0.3", "html-loader": "^1.3.2", diff --git a/src/app/core/export-service/export.service.ts b/src/app/core/export-service/export.service.ts index 7c7578f4760..53b53fc2df3 100644 --- a/src/app/core/export-service/export.service.ts +++ b/src/app/core/export-service/export.service.ts @@ -5,6 +5,7 @@ import { toJpeg, toPng } from 'html-to-image'; import { Options } from 'html-to-image/es/options'; import { saveAs } from 'file-saver'; import { BehaviorSubject } from 'rxjs'; +import { hasValue } from 'src/app/shared/empty.util'; export enum ExportImageType { png = 'png', @@ -69,4 +70,20 @@ export class ExportService { } + /** + * Creates an image from the given base64 string. + * @param base64 the base64 string + * @param type image type (png or jpeg) + * @param fileName + * @param isLoading + */ + exportImageWithBase64(base64: string, type: ExportImageType, fileName: string, isLoading: BehaviorSubject): void { + if (hasValue(base64)) { + saveAs(base64, fileName + '.' + type); + } else { + console.error('Base64 string is empty'); + } + + isLoading.next(false); + } } diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html index 62934b8924c..54af8dcd0e0 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.html @@ -1,13 +1,31 @@ -
- - -
-
- -
+ +
+
+
+ +
+ +
+
+
+
+ +
+ +
+
diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts index 6f0eead5f1b..9f803cf029a 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts @@ -1,10 +1,8 @@ -import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { UsageReport } from '../../../core/statistics/models/usage-report.model'; -import { GoogleChartInterface } from 'ng2-google-charts'; +import { GoogleChartComponent, GoogleChartInterface } from 'ng2-google-charts'; import { ExportImageType, ExportService } from '../../../core/export-service/export.service'; import { BehaviorSubject } from 'rxjs'; - - @Component({ selector: 'ds-statistics-map', templateUrl: './statistics-map.component.html', @@ -36,14 +34,20 @@ export class StatisticsMapComponent implements OnInit { * Loading utilized for export functions to disable buttons */ isLoading: BehaviorSubject = new BehaviorSubject(false); - /** - * Loading utilized for export functions to disable buttons - */ - isSecondLoading: BehaviorSubject = new BehaviorSubject(false); + + isLoading$: BehaviorSubject = new BehaviorSubject(false); + /** * Chart ElementRef */ - @ViewChild('googleChartRef') googleChartRef: ElementRef; + @ViewChild('googleChartRef') googleChartRef: GoogleChartComponent; + + exportImageType = ExportImageType; + + exportImageTypes = [ + { type: ExportImageType.png, label: 'PNG' }, + { type: ExportImageType.jpeg, label: 'JPEG/JPG' } + ]; constructor( private exportService: ExportService @@ -80,25 +84,16 @@ export class StatisticsMapComponent implements OnInit { ], options: { 'title': this.report.reportType } }; - } /** - * Download map as image in png version. + * Export the map as an image + * @param type of export */ - downloadPng() { - this.isLoading.next(false); - const node = this.googleChartRef.nativeElement; - this.exportService.exportAsImage(node, ExportImageType.png, this.report.reportType, this.isLoading); + exportMapAsImage(type: ExportImageType) { + this.isLoading$.next(true); + const chart = this.googleChartRef.wrapper.getChart(); + const imageURI: string = chart?.getImageURI(); + this.exportService.exportImageWithBase64(imageURI, type, this.report.reportType, this.isLoading$); } - - /** - * Download map as image in jpeg version. - */ - downloadJpeg() { - this.isSecondLoading.next(false); - const node = this.googleChartRef.nativeElement; - this.exportService.exportAsImage(node, ExportImageType.jpeg, this.report.reportType, this.isSecondLoading); - } - } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fec5973a533..3408366b70f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6181,5 +6181,7 @@ "invitation.ignore-btn": "Ignore", - "authority-confidence.search-label":"Search" + "authority-confidence.search-label":"Search", + + "statistics-page.export-map-as-image": "Export map", } diff --git a/yarn.lock b/yarn.lock index caae587bf34..439ccf953ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1494,14 +1494,14 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@es-joy/jsdoccomment@~0.22.1": - version "0.22.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.22.1.tgz#3c86d458780231769215a795105bd3b03b2616f2" - integrity sha512-/WMkqLYfwCf0waCAMC8Eddt3iAOdghkDF5vmyKEu8pfO66KRFY1L15yks8mfgURiwOAOJpAQ3blvB3Znj6ZwBw== +"@es-joy/jsdoccomment@~0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" + integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== dependencies: comment-parser "1.3.1" esquery "^1.4.0" - jsdoc-type-pratt-parser "~2.2.5" + jsdoc-type-pratt-parser "~3.1.0" "@eslint/eslintrc@^1.2.1": version "1.2.1" @@ -5965,18 +5965,17 @@ eslint-plugin-import@^2.25.4: resolve "^1.20.0" tsconfig-paths "^3.12.0" -eslint-plugin-jsdoc@^38.0.6: - version "38.0.6" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.0.6.tgz#b26843bdc445202b9f0e3830bda39ec5aacbfa97" - integrity sha512-Wvh5ERLUL8zt2yLZ8LLgi8RuF2UkjDvD+ri1/i7yMpbfreK2S29B9b5JC7iBIoFR7KDaEWCLnUPHTqgwcXX1Sg== +eslint-plugin-jsdoc@^39.6.4: + version "39.9.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.9.1.tgz#e9ce1723411fd7ea0933b3ef0dd02156ae3068e2" + integrity sha512-Rq2QY6BZP2meNIs48aZ3GlIlJgBqFCmR55+UBvaDkA3ZNQ0SvQXOs2QKkubakEijV8UbIVbVZKsOVN8G3MuqZw== dependencies: - "@es-joy/jsdoccomment" "~0.22.1" + "@es-joy/jsdoccomment" "~0.36.1" comment-parser "1.3.1" debug "^4.3.4" escape-string-regexp "^4.0.0" esquery "^1.4.0" - regextras "^0.8.0" - semver "^7.3.5" + semver "^7.3.8" spdx-expression-parse "^3.0.1" eslint-plugin-unused-imports@^2.0.0: @@ -8094,10 +8093,10 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdoc-type-pratt-parser@~2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz#c9f93afac7ee4b5ed4432fe3f09f7d36b05ed0ff" - integrity sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw== +jsdoc-type-pratt-parser@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" + integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== jsdom@19.0.0: version "19.0.0" @@ -9365,12 +9364,12 @@ ng2-file-upload@1.4.0: dependencies: tslib "^1.9.0" -ng2-google-charts@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ng2-google-charts/-/ng2-google-charts-6.2.0.tgz#c0b89e2b7bde0acbdca7489270e2bb8c91ba75bd" - integrity sha512-dbG93G22hDcNNfdR3vL1GyT8ez0N2J5c8UHrI0MO8+AdFWJm6v4iODRbTGrcUGWYxKsC4V5gRrzfVBtOhMnlhA== +ng2-google-charts@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ng2-google-charts/-/ng2-google-charts-7.0.0.tgz#1e601dad1d6f2f964d052a8f6eeb6d56efd04c1a" + integrity sha512-MRc7oIDAvFVdvW2SQvE8xNjzL5NDdnB4GvJEBRW8PYsuNH1gj3RlQRdFRDdkzaRDy4y9jVI6x6yZZAYP3ZCVLw== dependencies: - tslib "^1.9.0" + tslib "^2.3.0" ng2-nouislider@^1.8.3: version "1.8.3" @@ -11440,11 +11439,6 @@ regexpu-core@^5.0.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -regextras@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.8.0.tgz#ec0f99853d4912839321172f608b544814b02217" - integrity sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ== - registry-auth-token@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" @@ -11928,6 +11922,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.3.8: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" From 8eadb062d5ac333ce968a9cbcc81774d0a2403b0 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 30 May 2023 18:05:19 +0200 Subject: [PATCH 023/167] [DSC-38] ng2-google-charts downgrade --- package.json | 2 +- .../statistics-map.component.ts | 20 +++++++++++++++---- yarn.lock | 10 +++++----- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f14492785a2..872641ebe32 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "morgan": "^1.10.0", "ng-mocks": "^13.1.1", "ng2-file-upload": "1.4.0", - "ng2-google-charts": "^7.0.0", + "ng2-google-charts": "^6.1.0", "ng2-nouislider": "^1.8.3", "ngx-infinite-scroll": "^10.0.1", "ngx-pagination": "5.0.0", diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts index 9f803cf029a..46d28dd303b 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts @@ -1,8 +1,9 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, Input, OnInit, PLATFORM_ID, ViewChild } from '@angular/core'; import { UsageReport } from '../../../core/statistics/models/usage-report.model'; -import { GoogleChartComponent, GoogleChartInterface } from 'ng2-google-charts'; +import { GoogleChartComponent, GoogleChartInterface, GoogleChartType } from 'ng2-google-charts'; import { ExportImageType, ExportService } from '../../../core/export-service/export.service'; import { BehaviorSubject } from 'rxjs'; +import { isPlatformBrowser } from '@angular/common'; @Component({ selector: 'ds-statistics-map', templateUrl: './statistics-map.component.html', @@ -49,9 +50,20 @@ export class StatisticsMapComponent implements OnInit { { type: ExportImageType.jpeg, label: 'JPEG/JPG' } ]; + protected exportService: ExportService; + constructor( - private exportService: ExportService + @Inject(PLATFORM_ID) protected platformId: Object ) { + if (isPlatformBrowser(this.platformId)) { + import('../../../core/export-service/browser-export.service').then((s) => { + this.exportService = new s.BrowserExportService(); + }); + } else { + import('../../../core/export-service/server-export.service').then((s) => { + this.exportService = new s.ServerExportService(); + }); + } } ngOnInit(): void { @@ -77,7 +89,7 @@ export class StatisticsMapComponent implements OnInit { }); this.geoChart = { - chartType: 'GeoChart', + chartType: GoogleChartType.GeoChart, dataTable: [ this.chartColumns, ...this.data diff --git a/yarn.lock b/yarn.lock index 92b0dd79a03..fbb54046db0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8548,12 +8548,12 @@ ng2-file-upload@1.4.0: dependencies: tslib "^1.9.0" -ng2-google-charts@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/ng2-google-charts/-/ng2-google-charts-7.0.0.tgz#1e601dad1d6f2f964d052a8f6eeb6d56efd04c1a" - integrity sha512-MRc7oIDAvFVdvW2SQvE8xNjzL5NDdnB4GvJEBRW8PYsuNH1gj3RlQRdFRDdkzaRDy4y9jVI6x6yZZAYP3ZCVLw== +ng2-google-charts@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ng2-google-charts/-/ng2-google-charts-6.2.0.tgz#c0b89e2b7bde0acbdca7489270e2bb8c91ba75bd" + integrity sha512-dbG93G22hDcNNfdR3vL1GyT8ez0N2J5c8UHrI0MO8+AdFWJm6v4iODRbTGrcUGWYxKsC4V5gRrzfVBtOhMnlhA== dependencies: - tslib "^2.3.0" + tslib "^1.9.0" ng2-nouislider@^1.8.3: version "1.8.3" From ee8c3275fce0f604ca7fa9760226104497c1a2e6 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 30 May 2023 19:14:27 +0200 Subject: [PATCH 024/167] [DSC-38] Unit test fix --- .../statistics-map.component.spec.ts | 24 +++++++++---------- .../statistics-map.component.ts | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts index 0fa818149ed..62ff1e1e30e 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.spec.ts @@ -5,7 +5,7 @@ import { UsageReport } from '../../../core/statistics/models/usage-report.model' import { USAGE_REPORT } from '../../../core/statistics/models/usage-report.resource-type'; import { GoogleChartInterface } from 'ng2-google-charts'; -import { ExportService, ExportImageType } from '../../../core/export-service/export.service'; +import { ExportService } from '../../../core/export-service/export.service'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { StatisticsType } from '../statistics-type.model'; @@ -55,14 +55,17 @@ describe('StatisticsMapComponent', () => { const exportServiceMock: any = { exportAsImage: jasmine.createSpy('exportAsImage'), - exportAsFile: jasmine.createSpy('exportAsFile') + exportAsFile: jasmine.createSpy('exportAsFile'), + exportImageWithBase64: jasmine.createSpy('exportImageWithBase64') }; + + let exportService: ExportService = exportServiceMock; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [ StatisticsMapComponent ], providers: [ - { provide: ExportService, useValue: exportServiceMock } + // { provide: ExportService, useValue: exportServiceMock } ], }) .compileComponents(); @@ -71,6 +74,8 @@ describe('StatisticsMapComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(StatisticsMapComponent); component = fixture.componentInstance; + spyOn(component, 'exportMapAsImage'); + (component as any).exportService = exportServiceMock; fixture.detectChanges(); }); @@ -94,18 +99,11 @@ describe('StatisticsMapComponent', () => { it('should download map as png and jpg', () => { component.report = report; - fixture.detectChanges(); component.ngOnInit(); fixture.detectChanges(); - const downloadPngMapBtn = fixture.debugElement.query(By.css('[data-test="download-png-map-btn"]')); - downloadPngMapBtn.triggerEventHandler('click', null); - fixture.detectChanges(); - const node = fixture.debugElement.query(By.css('[data-test="google-chart-ref"]')).nativeElement; - expect(exportServiceMock.exportAsImage).toHaveBeenCalledWith(node, ExportImageType.png, report.reportType, component.isLoading); - - const downloadJpgMapBtn = fixture.debugElement.query(By.css('[data-test="download-jpg-map-btn"]')); - downloadJpgMapBtn.triggerEventHandler('click', null); + const drpdButton = fixture.debugElement.query(By.css('div[ngbdropdownmenu]>button[ngbdropdownitem]')); + drpdButton.triggerEventHandler('click', null); fixture.detectChanges(); - expect(exportServiceMock.exportAsImage).toHaveBeenCalledWith(node, ExportImageType.jpeg, report.reportType, component.isSecondLoading); + expect(component.exportMapAsImage).toHaveBeenCalled(); }); }); diff --git a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts index 46d28dd303b..fde75a47040 100644 --- a/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts +++ b/src/app/statistics-page/cris-statistics-page/statistics-map/statistics-map.component.ts @@ -102,7 +102,7 @@ export class StatisticsMapComponent implements OnInit { * Export the map as an image * @param type of export */ - exportMapAsImage(type: ExportImageType) { + exportMapAsImage(type: ExportImageType){ this.isLoading$.next(true); const chart = this.googleChartRef.wrapper.getChart(); const imageURI: string = chart?.getImageURI(); From f9627d556b6ab9717ba8d907fb9debf9760b18f6 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 31 May 2023 14:53:24 +0200 Subject: [PATCH 025/167] [DSC-381] Truncate breadcrumb text and show a tooltip for truncated ones --- .../breadcrumb/is-text-truncated.pipe.ts | 22 +++++++++++++++ ...runcate-breadcrumb-item-characters.pipe.ts | 28 +++++++++++++++++++ .../breadcrumbs/breadcrumbs.component.html | 17 +++++++++-- src/app/root.module.ts | 4 +++ src/config/app-config.interface.ts | 1 + src/config/default-app-config.ts | 1 + src/environments/environment.test.ts | 3 +- 7 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/app/breadcrumbs/breadcrumb/is-text-truncated.pipe.ts create mode 100644 src/app/breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe.ts diff --git a/src/app/breadcrumbs/breadcrumb/is-text-truncated.pipe.ts b/src/app/breadcrumbs/breadcrumb/is-text-truncated.pipe.ts new file mode 100644 index 00000000000..10481588267 --- /dev/null +++ b/src/app/breadcrumbs/breadcrumb/is-text-truncated.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { hasValue } from '../../shared/empty.util'; + +@Pipe({ + name: 'dsIsTextTruncated', +}) +export class IsTextTruncatedPipe implements PipeTransform { + + /** + * @param truncatedText Translated truncated text (text after TruncateBreadcrumbItemCharactersPipe transform) + * @param fullText Full translated text + * @returns {string} The full text if the truncated text contains an ellipses, otherwise an empty string. + * In case an empty string is returned the tooltip will not be shown. + */ + transform(truncatedText: string, fullText: string): string { + if (hasValue(truncatedText) && truncatedText.includes('...')) { + return fullText; + } else { + return ''; + } + } +} diff --git a/src/app/breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe.ts b/src/app/breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe.ts new file mode 100644 index 00000000000..e43ea1c7cf3 --- /dev/null +++ b/src/app/breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe.ts @@ -0,0 +1,28 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { environment } from '../../../environments/environment'; +import { hasValue } from '../../shared/empty.util'; + +@Pipe({ + name: 'dsTruncateText', +}) +export class TruncateBreadcrumbItemCharactersPipe implements PipeTransform { + /** + * The maximum number of characters to display in a breadcrumb item + * @type {number} + */ + readonly charLimit: number = environment.breadcrumbCharLimit; + + /** + * Truncates the text based on the configured char number allowed per breadcrumb element. + * If text is shorter than the number of chars allowed, it will return the text as it is. + * If text is longer than the number of chars allowed, it will return the text truncated with an ellipsis at the end. + * @param text Traslated text to be truncated + */ + transform(text: string): string { + if (hasValue(text) && text.length > this.charLimit) { + return text.substring(0, this.charLimit).concat('...'); + } else { + return text; + } + } +} diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html index 6f52b256479..5ea9b53b33f 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.html +++ b/src/app/breadcrumbs/breadcrumbs.component.html @@ -10,11 +10,24 @@ - + - + diff --git a/src/app/root.module.ts b/src/app/root.module.ts index 7671217c53b..84841ae7068 100644 --- a/src/app/root.module.ts +++ b/src/app/root.module.ts @@ -42,6 +42,8 @@ import { import { FooterModule } from './footer/footer.module'; import { SocialModule } from './social/social.module'; import { ExploreModule } from './shared/explore/explore.module'; +import { IsTextTruncatedPipe } from './breadcrumbs/breadcrumb/is-text-truncated.pipe'; +import { TruncateBreadcrumbItemCharactersPipe } from './breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe'; const IMPORTS = [ CommonModule, @@ -83,6 +85,8 @@ const DECLARATIONS = [ ThemedPageErrorComponent, PageErrorComponent, ContextHelpToggleComponent, + TruncateBreadcrumbItemCharactersPipe, + IsTextTruncatedPipe ]; const EXPORTS = [ diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index d0f336d2af4..441d8818c65 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -66,6 +66,7 @@ interface AppConfig extends Config { attachmentRendering: AttachmentRenderingConfig; advancedAttachmentRendering: AdvancedAttachmentRenderingConfig; searchResult: SearchResultConfig; + breadcrumbCharLimit: number; } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 1261ea04ec4..9f3b6dfcd1b 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -713,4 +713,5 @@ export class DefaultAppConfig implements AppConfig { additionalMetadataFields: [] }; + breadcrumbCharLimit = 10; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 7bdb8492324..a33e5694559 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -532,6 +532,7 @@ export const environment: BuildConfig = { metadataConfiguration: [] } ] - } + }, + breadcrumbCharLimit: 10, }; From 58d6417549e7e7d7b2f7807571c15370b760dcba Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 31 May 2023 16:02:13 +0200 Subject: [PATCH 026/167] [DSC-381] Fixed failing unit tests --- .../breadcrumbs/breadcrumbs.component.spec.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/breadcrumbs/breadcrumbs.component.spec.ts b/src/app/breadcrumbs/breadcrumbs.component.spec.ts index 69387e75346..7c80c87c3af 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.spec.ts +++ b/src/app/breadcrumbs/breadcrumbs.component.spec.ts @@ -10,22 +10,29 @@ import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock'; import { RouterTestingModule } from '@angular/router/testing'; import { of as observableOf } from 'rxjs'; import { DebugElement } from '@angular/core'; +import { IsTextTruncatedPipe } from './breadcrumb/is-text-truncated.pipe'; +import { TruncateBreadcrumbItemCharactersPipe } from './breadcrumb/truncate-breadcrumb-item-characters.pipe'; describe('BreadcrumbsComponent', () => { let component: BreadcrumbsComponent; let fixture: ComponentFixture; let breadcrumbsServiceMock: BreadcrumbsService; + let truncateTextPipe: TruncateBreadcrumbItemCharactersPipe; const expectBreadcrumb = (listItem: DebugElement, text: string, url: string) => { const anchor = listItem.query(By.css('a')); - + const truncatedText = truncateTextPipe.transform(text); if (url == null) { expect(anchor).toBeNull(); - expect(listItem.nativeElement.innerHTML).toEqual(text); + // remove leading whitespace characters + const textWithoutSpaces = listItem.nativeElement.innerHTML.trimStart().replace(/^\s+/, ''); + expect(textWithoutSpaces).toEqual(truncatedText); } else { expect(anchor).toBeInstanceOf(DebugElement); expect(anchor.attributes.href).toEqual(url); - expect(anchor.nativeElement.innerHTML).toEqual(text); + // remove leading whitespace characters + const textWithoutSpaces = anchor.nativeElement.innerHTML.trimStart().replace(/^\s+/, ''); + expect(textWithoutSpaces).toEqual(truncatedText); } }; @@ -43,6 +50,8 @@ describe('BreadcrumbsComponent', () => { declarations: [ BreadcrumbsComponent, VarDirective, + IsTextTruncatedPipe, + TruncateBreadcrumbItemCharactersPipe, ], imports: [ RouterTestingModule.withRoutes([]), @@ -55,10 +64,12 @@ describe('BreadcrumbsComponent', () => { ], providers: [ { provide: BreadcrumbsService, useValue: breadcrumbsServiceMock }, + { provide: TruncateBreadcrumbItemCharactersPipe, useClass: TruncateBreadcrumbItemCharactersPipe }, ], }).compileComponents(); fixture = TestBed.createComponent(BreadcrumbsComponent); + truncateTextPipe = TestBed.inject(TruncateBreadcrumbItemCharactersPipe); component = fixture.componentInstance; fixture.detectChanges(); })); From 43a03ac8e8f76354713612ef82f96f7a048d7fc9 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 2 Jun 2023 17:45:34 +0200 Subject: [PATCH 027/167] [DSC-106]Date input usable via keyboard using tab --- .../date-picker/date-picker.component.ts | 48 ++++++++++++++++++- .../number-picker.component.html | 4 +- .../number-picker/number-picker.component.ts | 1 + .../sections/form/section-form.component.ts | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.ts index 3ff94542a87..e34989f9765 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, HostListener, Inject, Input, OnInit, Output, Renderer2 } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { DynamicDsDatePickerModel } from './date-picker.model'; import { hasValue } from '../../../../../empty.util'; @@ -7,6 +7,8 @@ import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; +import { DOCUMENT } from '@angular/common'; +import { isEqual } from 'lodash'; export const DS_DATE_PICKER_SEPARATOR = '-'; @@ -51,7 +53,9 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement disabledMonth = true; disabledDay = true; constructor(protected layoutService: DynamicFormLayoutService, - protected validationService: DynamicFormValidationService + protected validationService: DynamicFormValidationService, + private renderer: Renderer2, + @Inject(DOCUMENT) private _document: Document ) { super(layoutService, validationService); } @@ -164,6 +168,46 @@ export class DsDatePickerComponent extends DynamicFormControlComponent implement this.change.emit(value); } + /** + * Listen to keydown Tab event. + * Get the active element and blur it, in order to focus the next input field. + */ + @HostListener('keydown', ['$event']) + onKeyDown(event: KeyboardEvent) { + if (event.key === 'Tab') { + event.preventDefault(); + const activeElement: Element = this._document.activeElement; + (activeElement as any).blur(); + if (isEqual(activeElement.id, this.model.id.concat('_year')) ) { + this.focusInput('_month'); + } else if (isEqual(activeElement.id, this.model.id.concat('_month'))) { + this.focusInput('_day'); + } + } + } + + /** + * Focus the input field for the given type + * based on the model id. + * Used to focus the next input field + * in case of a disabled field. + * @param type '_month' | '_day' + */ + focusInput(type: '_month' | '_day') { + const field = this._document.getElementById(this.model.id.concat(type)); + if (field) { + + if (hasValue(this.year) && isEqual(type, '_month')) { + this.disabledMonth = false; + } else if (hasValue(this.month) && isEqual(type, '_day')) { + this.disabledDay = false; + } + setTimeout(() => { + this.renderer.selectRootElement(field).focus(); + }, 100); + } + } + onFocus(event) { this.focus.emit(event); } diff --git a/src/app/shared/form/number-picker/number-picker.component.html b/src/app/shared/form/number-picker/number-picker.component.html index 314d9d3b70b..2353a6533cf 100644 --- a/src/app/shared/form/number-picker/number-picker.component.html +++ b/src/app/shared/form/number-picker/number-picker.component.html @@ -20,7 +20,7 @@ -
- - - - +
+
+
+ + + +
diff --git a/src/app/shared/search-form/search-form.component.scss b/src/app/shared/search-form/search-form.component.scss index cf3a354364f..6e73b4b960f 100644 --- a/src/app/shared/search-form/search-form.component.scss +++ b/src/app/shared/search-form/search-form.component.scss @@ -7,3 +7,7 @@ .scope-button { max-width: var(--ds-search-form-scope-max-width); } + +.form-group { // add margin to parent components + margin-bottom: 0; +} diff --git a/src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.html b/src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.html index 3d43a7d6c15..66c44b0fbd1 100644 --- a/src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.html +++ b/src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.html @@ -3,7 +3,7 @@
- + (changeSecurityLevel)="addSecurityLevelToMetadata($event)"> + +
+
+ +
+
+ + + + + + + +
+
+
- - - - - - - -
-
- +
diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index b9ea9895304..29351a7fd81 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -11,20 +11,36 @@ (dfChange)="onChange($event)" (dfFocus)="onFocus($event)" (ngbEvent)="onCustomEvent($event)"> + + +
+ +
+
+
- -
+ +
diff --git a/src/app/shared/form/form.component.spec.ts b/src/app/shared/form/form.component.spec.ts index 2f3be3fded3..be29f7d31f0 100644 --- a/src/app/shared/form/form.component.spec.ts +++ b/src/app/shared/form/form.component.spec.ts @@ -4,9 +4,11 @@ import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { + DynamicFormArrayGroupModel, DynamicFormArrayModel, DynamicFormControlEvent, DynamicFormControlModel, + DynamicFormGroupModel, DynamicFormValidationService, DynamicInputModel } from '@ng-dynamic-forms/core'; @@ -24,6 +26,11 @@ import { FormFieldMetadataValueObject } from './builder/models/form-field-metada import { createTestComponent } from '../testing/utils.test'; import { BehaviorSubject } from 'rxjs'; import { storeModuleConfig } from '../../app.reducer'; +import { + DynamicScrollableDropdownModel, + DynamicScrollableDropdownModelConfig +} from './builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model'; +import { DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core/lib/model/form-group/dynamic-form-group.model'; let TEST_FORM_MODEL; @@ -433,6 +440,167 @@ describe('FormComponent test suite', () => { expect(formComp.removeArrayItem.emit).toHaveBeenCalled(); })); + + it('should emit removeArrayItem Event when an scrollable dropdown field has been cleaned', inject([FormBuilderService], (service: FormBuilderService) => { + spyOn(formComp.removeArrayItem, 'emit'); + + formComp.clearScrollableDropdown(new Event('click'), formComp.formModel[0] as DynamicFormControlModel); + + expect(formComp.removeArrayItem.emit).toHaveBeenCalled(); + })); + }); + + describe('isArrayGroupEmpty', () => { + init(); + beforeEach(() => { + formFixture = TestBed.createComponent(FormComponent); + store = TestBed.inject(Store as any); + formComp = formFixture.componentInstance; + formComp.formId = 'testFormArray'; + formComp.formModel = TEST_FORM_MODEL_WITH_ARRAY; + formComp.displaySubmit = false; + formComp.displayCancel = false; + formFixture.detectChanges(); + spyOn(store, 'dispatch'); + }); + + afterEach(() => { + formFixture.destroy(); + formComp = null; + }); + + it('should return false if array group has multiple values', () => { + const group = { + context: { + groups: [ + { + group: [ + { + id: 'groupId', + value: 'groupValue1' + } + ], + }, + { + group: [ + { + id: 'groupId', + value: 'groupValue2' + } + ], + } + ] + } + }; + + const result = formComp.isArrayGroupEmpty(group); + + expect(result).toBeFalse(); + }); + + it('should return false if array group has only one value', () => { + const group = { + context: { + groups: [ + { + group: [ + { + id: 'groupId', + value: 'groupValue1' + } + ], + } + ] + } + }; + + const result = formComp.isArrayGroupEmpty(group); + + expect(result).toBeFalse(); + }); + + it('should return true if array group has one group but without value', () => { + const group = { + context: { + groups: [ + { + group: [ + { + id: 'groupId', + value: null + } + ], + } + ] + } + }; + + const result = formComp.isArrayGroupEmpty(group); + + expect(result).toBeTrue(); + }); + + it('should return true if array group does not have any value', () => { + const group = { + context: { + groups: [] + } + }; + + const result = formComp.isArrayGroupEmpty(group); + + expect(result).toBeTrue(); + }); + }); + + describe('isTheOnlyFieldInArrayGroup', () => { + init(); + beforeEach(() => { + formFixture = TestBed.createComponent(FormComponent); + store = TestBed.inject(Store as any); + formComp = formFixture.componentInstance; + formComp.formId = 'testFormArray'; + formComp.formModel = TEST_FORM_MODEL_WITH_ARRAY; + formComp.displaySubmit = false; + formComp.displayCancel = false; + formFixture.detectChanges(); + spyOn(store, 'dispatch'); + }); + + afterEach(() => { + formFixture.destroy(); + formComp = null; + }); + + it('should return true if it is the only field in array group', () => { + const parent = new DynamicFormArrayGroupModel({} as DynamicFormArrayModel, [{} as DynamicFormControlModel]); + const model = new DynamicScrollableDropdownModel({} as DynamicScrollableDropdownModelConfig); + model.parent = parent; + + const result = formComp.isTheOnlyFieldInArrayGroup(model); + + expect(result).toBeTrue(); + }); + + it('should return false if it is not the only field in array group', () => { + const parent = new DynamicFormArrayGroupModel({} as DynamicFormArrayModel, [{} as DynamicFormControlModel, {} as DynamicFormControlModel]); + const model = new DynamicScrollableDropdownModel({} as DynamicScrollableDropdownModelConfig); + model.parent = parent; + + const result = formComp.isTheOnlyFieldInArrayGroup(model); + + expect(result).toBeFalse(); + }); + + it('should return false if it is a field in not array group', () => { + const parent = new DynamicFormGroupModel({} as DynamicFormGroupModelConfig); + const model = new DynamicScrollableDropdownModel({} as DynamicScrollableDropdownModelConfig); + model.parent = parent; + + const result = formComp.isTheOnlyFieldInArrayGroup(model); + + expect(result).toBeFalse(); + }); }); }); diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 1d06d2cf7d7..05f88c095eb 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -4,6 +4,7 @@ import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/for import { Observable, Subscription } from 'rxjs'; import { + DynamicFormArrayGroupModel, DynamicFormArrayModel, DynamicFormControlEvent, DynamicFormControlModel, @@ -18,6 +19,10 @@ import { hasValue, isNotEmpty, isNotNull, isNull } from '../empty.util'; import { FormService } from './form.service'; import { FormEntry, FormError } from './form.reducer'; import { FormFieldMetadataValueObject } from './builder/models/form-field-metadata-value.model'; +import cloneDeep from 'lodash/cloneDeep'; +import { + DynamicScrollableDropdownModel +} from './builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model'; /** * The default form component. @@ -297,7 +302,7 @@ export class FormComponent implements OnDestroy, OnInit { if (this.emitChange) { this.change.emit(event); } -} + } /** * Method called on submit. @@ -332,7 +337,14 @@ export class FormComponent implements OnDestroy, OnInit { // In case of qualdrop value or inline-group remove event must be dispatched before removing the control from array this.removeArrayItem.emit(event); } - this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext); + if (index === 0 && formArrayControl.value?.length === 1) { + event.model = cloneDeep(event.model); + const fieldId = event.model.id; + formArrayControl.at(0).get(fieldId).setValue(null); + } else { + this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext); + } + this.formService.changeForm(this.formId, this.formModel); if (!this.formBuilderService.isQualdropGroup(event.model as DynamicFormControlModel) && !this.isInlineGroupForm) { // dispatch remove event for any field type except for qualdrop value and inline-group @@ -340,6 +352,14 @@ export class FormComponent implements OnDestroy, OnInit { } } + clearScrollableDropdown($event, model: DynamicFormControlModel): void { + const control = this.formGroup.get(this.formBuilderService.getPath(model)) as FormControl; + const event = { $event, type: 'remove', model: cloneDeep(model), context: null, control, group: control.parent } as DynamicFormControlEvent; + control.setValue(null); + this.formService.changeForm(this.formId, this.formModel); + this.removeArrayItem.emit(event); + } + insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void { const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray; this.formBuilderService.insertFormArrayGroup(index, formArrayControl, arrayContext); @@ -362,6 +382,14 @@ export class FormComponent implements OnDestroy, OnInit { return isNotEmpty(value) && value.isVirtual; } + isArrayGroupEmpty(group): boolean { + return group.context.groups?.length <= 1 && !group.context.groups?.[0]?.group?.[0]?.value; + } + + isTheOnlyFieldInArrayGroup(model: DynamicScrollableDropdownModel) { + return model.parent instanceof DynamicFormArrayGroupModel && model.parent?.group?.length === 1; + } + protected getEvent($event: any, arrayContext: DynamicFormArrayModel, index: number, type: string, formGroup?: FormGroup): DynamicFormControlEvent { const context = arrayContext.groups[index]; const itemGroupModel = context.context; diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 03310df8ca2..fedc5a26dfb 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -322,7 +322,7 @@ export class SectionFormOperationsService { } else if (event.context && event.context instanceof DynamicFormArrayGroupModel) { // Model is a DynamicRowArrayModel this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context, previousValue); - } else if ((isNotEmpty(value) && typeof value === 'string') || (isNotEmpty(value) && value instanceof FormFieldMetadataValueObject && value.hasValue())) { + } else if ((isNotEmpty(value) && typeof value === 'string') || (isNotEmpty(value) && (value instanceof FormFieldMetadataValueObject || value instanceof VocabularyEntry) && value.hasValue())) { this.operationsBuilder.remove(pathCombiner.getPath(path)); } } diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index fb40e71e94d..d1e4a9fc715 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -585,11 +585,14 @@ describe('SubmissionSectionFormComponent test suite', () => { it('should call dispatchOperationsFromEvent on form remove event', () => { spyOn(comp, 'hasStoredValue').and.returnValue(false); + formBuilderService.hasMappedGroupValue.and.returnValue(false); + formOperationsService.getFieldValueFromChangeEvent.and.returnValue('test'); comp.onRemove(dynamicFormControlEvent); expect(formOperationsService.dispatchOperationsFromEvent).toHaveBeenCalled(); - + expect(compAsAny.previousValue.path).toBeNull(); + expect(compAsAny.previousValue.value).toBeNull(); }); it('should check if has stored value in the section state', () => { diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index 3550a11c56b..39329e61b4c 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -429,6 +429,10 @@ export class SubmissionSectionFormComponent extends SectionModelComponent implem * the [[DynamicFormControlEvent]] emitted */ onFocus(event: DynamicFormControlEvent): void { + this.updatePreviousValue(event); + } + + private updatePreviousValue(event: DynamicFormControlEvent): void { const value = this.formOperationsService.getFieldValueFromChangeEvent(event); const path = this.formBuilderService.getPath(event.model); if (this.formBuilderService.hasMappedGroupValue(event.model)) { @@ -440,6 +444,11 @@ export class SubmissionSectionFormComponent extends SectionModelComponent implem } } + private clearPreviousValue(): void { + this.previousValue.path = null; + this.previousValue.value = null; + } + /** * Method called when a form remove event is fired. * Dispatch form operations based on changes. @@ -448,6 +457,7 @@ export class SubmissionSectionFormComponent extends SectionModelComponent implem * the [[DynamicFormControlEvent]] emitted */ onRemove(event: DynamicFormControlEvent): void { + this.updatePreviousValue(event); const fieldId = this.formBuilderService.getId(event.model); const fieldIndex = this.formOperationsService.getArrayIndexFromEvent(event); @@ -465,7 +475,7 @@ export class SubmissionSectionFormComponent extends SectionModelComponent implem event, this.previousValue, this.hasStoredValue(fieldId, fieldIndex)); - + this.clearPreviousValue(); } /** From c99fb5f544a7a6abce92e406e4562ffd25bf7e6d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 26 Jul 2023 15:14:54 +0200 Subject: [PATCH 039/167] refactoring of advanced attachment rendering component's location --- .../attachment-render/attachment-rendering.module.ts | 8 ++++---- .../file-download-button.component.html | 0 .../file-download-button.component.scss | 0 .../file-download-button.component.spec.ts | 4 ++-- .../file-download-button.component.ts | 7 +++---- 5 files changed, 9 insertions(+), 10 deletions(-) rename src/app/{shared => cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types}/file-download-button/file-download-button.component.html (100%) rename src/app/{shared => cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types}/file-download-button/file-download-button.component.scss (100%) rename src/app/{shared => cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types}/file-download-button/file-download-button.component.spec.ts (93%) rename src/app/{shared => cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types}/file-download-button/file-download-button.component.ts (67%) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/attachment-rendering.module.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/attachment-rendering.module.ts index 1c8bdc4c502..90fc221b5b5 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/attachment-rendering.module.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/attachment-rendering.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { - FileDownloadButtonComponent -} from '../../../../../../../../../shared/file-download-button/file-download-button.component'; + +import { TranslateModule } from '@ngx-translate/core'; + +import { FileDownloadButtonComponent } from './types/file-download-button/file-download-button.component'; import { SearchModule } from '../../../../../../../../../shared/search/search.module'; import { SharedModule } from '../../../../../../../../../shared/shared.module'; -import { TranslateModule } from '@ngx-translate/core'; const COMPONENTS = [ FileDownloadButtonComponent diff --git a/src/app/shared/file-download-button/file-download-button.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.html similarity index 100% rename from src/app/shared/file-download-button/file-download-button.component.html rename to src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.html diff --git a/src/app/shared/file-download-button/file-download-button.component.scss b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.scss similarity index 100% rename from src/app/shared/file-download-button/file-download-button.component.scss rename to src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.scss diff --git a/src/app/shared/file-download-button/file-download-button.component.spec.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.spec.ts similarity index 93% rename from src/app/shared/file-download-button/file-download-button.component.spec.ts rename to src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.spec.ts index 423b117544c..b6883e74233 100644 --- a/src/app/shared/file-download-button/file-download-button.component.spec.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.spec.ts @@ -6,8 +6,8 @@ import { of } from 'rxjs'; import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; import { Bitstream } from 'src/app/core/shared/bitstream.model'; import { Item } from 'src/app/core/shared/item.model'; -import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; -import { SharedModule } from '../shared.module'; +import { TranslateLoaderMock } from '../../../../../../../../../../../shared/mocks/translate-loader.mock'; +import { SharedModule } from '../../../../../../../../../../../shared/shared.module'; import { FileDownloadButtonComponent } from './file-download-button.component'; diff --git a/src/app/shared/file-download-button/file-download-button.component.ts b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.ts similarity index 67% rename from src/app/shared/file-download-button/file-download-button.component.ts rename to src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.ts index 5a560117f80..390856f1fa2 100644 --- a/src/app/shared/file-download-button/file-download-button.component.ts +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/types/file-download-button/file-download-button.component.ts @@ -1,9 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import { FileDownloadLinkComponent } from '../file-download-link/file-download-link.component'; import { - AttachmentRenderingType, - AttachmentTypeRendering -} from '../../cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-type.decorator'; + FileDownloadLinkComponent +} from '../../../../../../../../../../../shared/file-download-link/file-download-link.component'; +import { AttachmentRenderingType, AttachmentTypeRendering } from '../../../attachment-type.decorator'; @Component({ selector: 'ds-file-download-button', From 2122d84342176637ce897cf31f0221f04ea26c4c Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 26 Apr 2023 15:33:02 +0200 Subject: [PATCH 040/167] [DURACOM-134] send-back action fixed --- .../workflow-item-action-page.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts index b8998a6dd72..2ed5639c5a7 100644 --- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts +++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Location } from '@angular/common'; -import { Observable, forkJoin } from 'rxjs'; +import { Observable, combineLatest } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkflowItem } from '../core/submission/models/workflowitem.model'; @@ -52,7 +52,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { * Performs the action and shows a notification based on the outcome of the action */ performAction() { - forkJoin([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe( + combineLatest([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe( take(1), switchMap(([wfi]) => this.sendRequest(wfi.id)) ).subscribe((successful: boolean) => { From 86d41f17d7f55e00ab97524733f8e8aead7c704d Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 28 Apr 2023 18:03:52 +0200 Subject: [PATCH 041/167] [DURACOM-134] Administer workflow actions fixed --- ...m-admin-workflow-actions.component.spec.ts | 4 +- ...e-item-admin-workflow-actions.component.ts | 6 +- .../workflowitems-edit-page-routing-paths.ts | 7 +- ...ed-workspaceitems-delete-page.component.ts | 26 ++++ .../workspaceitems-delete-page.component.html | 24 ++++ .../workspaceitems-delete-page.component.scss | 4 + ...rkspaceitems-delete-page.component.spec.ts | 110 +++++++++++++++++ .../workspaceitems-delete-page.component.ts | 111 ++++++++++++++++++ ...workspaceitems-edit-page-routing.module.ts | 24 +++- .../workspaceitems-edit-page.module.ts | 7 +- src/assets/i18n/en.json5 | 16 ++- .../workspace-items-delete.component.html | 0 .../workspace-items-delete.component.scss | 0 .../workspace-items-delete.component.ts | 10 ++ src/themes/custom/lazy-theme.module.ts | 4 +- 15 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-delete-page/themed-workspaceitems-delete-page.component.ts create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.scss create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts create mode 100644 src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts create mode 100644 src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.html create mode 100644 src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.scss create mode 100644 src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.ts diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.spec.ts index 628fc3f89ca..a8f0581ec0e 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.spec.ts @@ -11,7 +11,7 @@ import { URLCombiner } from '../../../../../core/url-combiner/url-combiner'; import { WorkspaceItemAdminWorkflowActionsComponent } from './workspace-item-admin-workflow-actions.component'; import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { - getWorkflowItemDeleteRoute, + getWorkspaceItemDeleteRoute, } from '../../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; import { Item } from '../../../../../core/shared/item.model'; import { RemoteData } from '../../../../../core/data/remote-data'; @@ -83,7 +83,7 @@ describe('WorkspaceItemAdminWorkflowActionsComponent', () => { it('should render a delete button with the correct link', () => { const button = fixture.debugElement.query(By.css('a.delete-link')); const link = button.nativeElement.href; - expect(link).toContain(new URLCombiner(getWorkflowItemDeleteRoute(wsi.id)).toString()); + expect(link).toContain(new URLCombiner(getWorkspaceItemDeleteRoute(wsi.id)).toString()); }); it('should render a policies button with the correct link', () => { diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.ts index adbd4216289..36678460da1 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.ts @@ -11,7 +11,7 @@ import { SupervisionOrderGroupSelectorComponent } from './supervision-order-group-selector/supervision-order-group-selector.component'; import { - getWorkflowItemDeleteRoute + getWorkspaceItemDeleteRoute } from '../../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../../../item-page/edit-item-page/edit-item-page.routing-paths'; import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; @@ -105,10 +105,10 @@ export class WorkspaceItemAdminWorkflowActionsComponent implements OnInit { } /** - * Returns the path to the delete page of this workflow item + * Returns the path to the delete page of this workspace item */ getDeleteRoute(): string { - return getWorkflowItemDeleteRoute(this.wsi.id); + return getWorkspaceItemDeleteRoute(this.wsi.id); } /** diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts index ece61f0321c..326eebe4a79 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts @@ -1,5 +1,5 @@ import { URLCombiner } from '../core/url-combiner/url-combiner'; -import { getWorkflowItemModuleRoute } from '../app-routing-paths'; +import { getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from '../app-routing-paths'; export function getWorkflowItemPageRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModuleRoute(), wfiId).toString(); @@ -24,8 +24,13 @@ export function getAdvancedWorkflowRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, ADVANCED_WORKFLOW_PATH).toString(); } +export function getWorkspaceItemDeleteRoute(wsiId: string) { + return new URLCombiner(getWorkspaceItemModuleRoute(), wsiId, WORKSPACE_ITEM_DELETE_PATH).toString(); +} + export const WORKFLOW_ITEM_EDIT_PATH = 'edit'; export const WORKFLOW_ITEM_DELETE_PATH = 'delete'; export const WORKFLOW_ITEM_VIEW_PATH = 'view'; export const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback'; export const ADVANCED_WORKFLOW_PATH = 'advanced'; +export const WORKSPACE_ITEM_DELETE_PATH = 'delete'; diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/themed-workspaceitems-delete-page.component.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/themed-workspaceitems-delete-page.component.ts new file mode 100644 index 00000000000..681cba21c86 --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/themed-workspaceitems-delete-page.component.ts @@ -0,0 +1,26 @@ +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { Component } from '@angular/core'; +import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component'; + +/** + * Themed wrapper for WorkspaceItemsDeletePageComponent + */ + +@Component({ + selector: 'ds-themed-workspace-items-delete', + styleUrls: [], + templateUrl: './../../shared/theme-support/themed.component.html' +}) +export class ThemedWorkspaceItemsDeletePageComponent extends ThemedComponent { + protected getComponentName(): string { + return 'WorkspaceItemsDeletePageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./workspaceitems-delete-page.component`); + } +} diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html new file mode 100644 index 00000000000..a0f0a1711ec --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html @@ -0,0 +1,24 @@ +
+

{{ 'workspace-item.delete.header' | translate }}

+ + + +
+ + + + + + diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.scss b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.scss new file mode 100644 index 00000000000..e52175abeaa --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.scss @@ -0,0 +1,4 @@ + +:host ::ng-deep ds-modify-item-overview table { + display: inline-table !important; +} diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts new file mode 100644 index 00000000000..f52dd497d8a --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -0,0 +1,110 @@ +import { RouteService } from '../..//core/services/route.service'; +import { NotificationsService } from '../..//shared/notifications/notifications.service'; +import { WorkspaceitemDataService } from '../..//core/submission/workspaceitem-data.service'; +import { RouterMock } from './../../shared/mocks/router.mock'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { Location } from '@angular/common'; +import { of as observableOf } from 'rxjs'; +import { routeServiceStub } from '../../shared/testing/route-service.stub'; +import { LocationStub } from '../../shared/testing/location.stub'; +import { By } from '@angular/platform-browser'; +import { ActivatedRouteStub } from 'src/app/shared/testing/active-router.stub'; +import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils'; +import { WorkspaceItem } from 'src/app/core/submission/models/workspaceitem.model'; +import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; + +describe('WorkspaceitemsDeletePageComponent', () => { + let component: WorkspaceItemsDeletePageComponent; + let fixture: ComponentFixture; + + const workspaceitemDataServiceSpy = jasmine.createSpyObj('WorkspaceitemDataService', { + delete: jasmine.createSpy('delete') + }); + + const wsi = new WorkspaceItem(); + wsi.id = '1234'; + const dso = new DSpaceObject(); + dso.uuid = '1234'; + + const translateServiceStub = { + get: () => observableOf('test-message'), + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + + const modalService = { + open: () => {/** empty */}, + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [WorkspaceItemsDeletePageComponent], + providers: [ + { + provide: ActivatedRoute, + useValue: new ActivatedRouteStub( + {}, + { + wsi: createSuccessfulRemoteDataObject(wsi), + dso: createSuccessfulRemoteDataObject(dso), + } + ), + }, + { provide: Router, useValue: new RouterMock() }, + { + provide: WorkspaceitemDataService, + useValue: workspaceitemDataServiceSpy, + }, + { provide: Location, useValue: new LocationStub() }, + { provide: NgbModal, useValue: modalService }, + { + provide: NotificationsService, + useValue: new NotificationsServiceStub(), + }, + { provide: TranslateService, useValue: translateServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkspaceItemsDeletePageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have the current WorkspaceItem', () => { + console.log( (component as any).activatedRoute, 'data wsi'); + (component as any).activatedRoute.data.subscribe((data) => { + console.log(data, 'dataaa'); + expect(data.wsi.payload.id).toEqual('1234'); + }); + }); + + it('should delete the target workspace item', () => { + spyOn((component as any).modalService, 'open').and.returnValue({}); + component.confirmDelete(By.css('#delete-modal')); + fixture.detectChanges(); + expect((component as any).modalService.open).toHaveBeenCalled(); + }); + + it('should call workspaceItemService.delete', () => { + spyOn(workspaceitemDataServiceSpy, 'delete').and.returnValue(observableOf(createSuccessfulRemoteDataObject({}))); + component.sendDeleteRequest(); + expect((component as any).workspaceItemService.delete).toHaveBeenCalledWith('1234'); + }); +}); diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts new file mode 100644 index 00000000000..cff5fd6994c --- /dev/null +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts @@ -0,0 +1,111 @@ +import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; +import { NoContent } from './../../core/shared/NoContent.model'; +import { RouteService } from 'src/app/core/services/route.service'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from './../../core/shared/operators'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { Component, OnInit } from '@angular/core'; +import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; +import { Observable, map, switchMap, take } from 'rxjs'; +import { ActivatedRoute, Data, Params, Router } from '@angular/router'; +import { Location } from '@angular/common'; +import { WorkspaceitemDataService } from 'src/app/core/submission/workspaceitem-data.service'; +import { TranslateService } from '@ngx-translate/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-workspaceitems-delete-page', + templateUrl: './workspaceitems-delete-page.component.html', + styleUrls: ['./workspaceitems-delete-page.component.scss'] +}) +export class WorkspaceItemsDeletePageComponent implements OnInit { + + /** + * The workspaceitem to delete + */ + public wsi$: Observable; + + /** + * The dspace object + */ + public dso$: Observable; + + /** + * The previous query parameters + */ + private previousQueryParameters?: Params; + + constructor( + private activatedRoute: ActivatedRoute, + private router: Router, + private routeService: RouteService, + private workspaceItemService: WorkspaceitemDataService, + private notificationsService: NotificationsService, + private translationService: TranslateService, + private location: Location, + private modalService: NgbModal, + ) { } + + ngOnInit(): void { + this.wsi$ = this.activatedRoute.data.pipe(map((data: Data) => data.wsi as RemoteData), getRemoteDataPayload()); + this.dso$ = this.activatedRoute.data.pipe(map((data: Data) => data.dso as RemoteData), getRemoteDataPayload()); + this.previousQueryParameters = (this.location.getState() as { [key: string]: any }).previousQueryParams; + } + + /** + * Navigates to the previous url + * If there's not previous url, it continues to the mydspace page instead + */ + previousPage() { + this.routeService.getPreviousUrl().pipe(take(1)) + .subscribe((url: string) => { + let params: Params = {}; + if (!url) { + url = '/mydspace'; + params = this.previousQueryParameters; + } + if (url.split('?').length > 1) { + for (const param of url.split('?')[1].split('&')) { + params[param.split('=')[0]] = decodeURIComponent(param.split('=')[1]); + } + } + void this.router.navigate([url.split('?')[0]], { queryParams: params }); + } + ); + } + + /** + * Open the modal to confirm the deletion of the workspaceitem + */ + public async confirmDelete(content) { + await this.modalService.open(content).result.then( + (result) => { + if (result === 'ok') { + this.sendDeleteRequest(); + } + } + ); + } + + /** + * Delete the target workspaceitem object + */ + sendDeleteRequest() { + this.wsi$.pipe( + switchMap((wsi: WorkspaceItem) => this.workspaceItemService.delete(wsi.id).pipe( + getFirstCompletedRemoteData(), + )) + ).subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + const title = this.translationService.get('workspace-item.delete.notification.success.title'); + const content = this.translationService.get('workspace-item.delete.title'); + this.notificationsService.success(title, content); + this.previousPage(); + } else { + const title = this.translationService.get('workspace-item.delete.notification.error.title'); + const content = this.translationService.get('workspace-item.delete.notification.error.content'); + this.notificationsService.error(title, content); + } + }); + } +} diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts index a66bec9f6b6..51edeb9ea77 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts @@ -8,6 +8,8 @@ import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item- import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver'; import { WorkspaceItemPageResolver } from './workspace-item-page.resolver'; import { PendingChangesGuard } from '../submission/edit/pending-changes/pending-changes.guard'; +import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/workspaceitems-delete-page.component'; +import { ThemedWorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/themed-workspaceitems-delete-page.component'; @NgModule({ imports: [ @@ -36,7 +38,27 @@ import { PendingChangesGuard } from '../submission/edit/pending-changes/pending- breadcrumb: I18nBreadcrumbResolver }, data: { title: 'workspace-item.view.title', breadcrumbKey: 'workspace-item.view' } - } + }, + { + canActivate: [AuthenticatedGuard], + path: 'delete', + component: WorkspaceItemsDeletePageComponent, + resolve: { + dso: ItemFromWorkspaceResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' } + }, + { + canActivate: [AuthenticatedGuard], + path: 'delete', + component: ThemedWorkspaceItemsDeletePageComponent, + resolve: { + dso: ItemFromWorkspaceResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' } + }, ] } ]) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts index 65a40f3f7c8..77cb7a6e66c 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page.module.ts @@ -3,6 +3,8 @@ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { WorkspaceitemsEditPageRoutingModule } from './workspaceitems-edit-page-routing.module'; import { SubmissionModule } from '../submission/submission.module'; +import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/workspaceitems-delete-page.component'; +import { ThemedWorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/themed-workspaceitems-delete-page.component'; @NgModule({ imports: [ @@ -11,7 +13,10 @@ import { SubmissionModule } from '../submission/submission.module'; SharedModule, SubmissionModule, ], - declarations: [] + declarations: [ + WorkspaceItemsDeletePageComponent, + ThemedWorkspaceItemsDeletePageComponent, + ] }) /** * This module handles all modules that need to access the workspaceitems edit page. diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2ffd38e2030..a8b00470498 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6729,9 +6729,23 @@ "workspace-item.view.title": "Workspace View", + "workspace-item.delete.breadcrumbs": "Workspace Delete", - "workflow-item.advanced.title": "Advanced workflow", + "workspace-item.delete.header": "Delete workspace item", + + "workspace-item.delete.button.confirm": "Delete", + + "workspace-item.delete.button.cancel": "Cancel", + + "workspace-item.delete.notification.success.title": "Deleted", + "workspace-item.delete.title": "This workspace item was successfully deleted", + + "workspace-item.delete.notification.error.title": "Something went wrong", + + "workspace-item.delete.notification.error.content": "The workspace item could not be deleted", + + "workflow-item.advanced.title": "Advanced workflow", "workflow-item.selectrevieweraction.notification.success.title": "Selected reviewer", diff --git a/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.html b/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.scss b/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.ts b/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.ts new file mode 100644 index 00000000000..95494d76dd3 --- /dev/null +++ b/src/themes/custom/app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { WorkspaceItemsDeletePageComponent as BaseComponent } from '../../../../../app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component'; + + +@Component({ + selector: 'ds-workspaceitems-delete-page', + templateUrl: '../../../../../app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html', +}) +export class WorkspaceItemsDeletePageComponent extends BaseComponent { +} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index d26659e3473..1b2ce85ff85 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -137,6 +137,7 @@ import { DsoEditMetadataComponent } from './app/dso-shared/dso-edit-metadata/dso import { DsoSharedModule } from '../../app/dso-shared/dso-shared.module'; import { SystemWideAlertModule } from '../../app/system-wide-alert/system-wide-alert.module'; import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module'; +import { WorkspaceItemsDeletePageComponent } from './app/workspace-items-delete-page/workspace-items-delete/workspace-items-delete.component'; const DECLARATIONS = [ FileSectionComponent, @@ -193,7 +194,8 @@ const DECLARATIONS = [ ExternalSourceEntryImportModalComponent, ResultsBackButtonComponent, DsoEditMetadataComponent, - BrowseMostElementsComponent + BrowseMostElementsComponent, + WorkspaceItemsDeletePageComponent, ]; @NgModule({ From 30d11d5815cd60119014490e983c283ff4cb3a91 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 2 May 2023 15:49:41 +0200 Subject: [PATCH 042/167] [DURACOM-134] Fix import paths --- .../workspaceitems-delete-page.component.spec.ts | 16 ++++++++-------- .../workspaceitems-delete-page.component.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts index f52dd497d8a..fc0bef83f0f 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -1,7 +1,7 @@ -import { RouteService } from '../..//core/services/route.service'; -import { NotificationsService } from '../..//shared/notifications/notifications.service'; -import { WorkspaceitemDataService } from '../..//core/submission/workspaceitem-data.service'; -import { RouterMock } from './../../shared/mocks/router.mock'; +import { RouteService } from '../../core/services/route.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service'; +import { RouterMock } from '../../shared/mocks/router.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component'; @@ -15,10 +15,10 @@ import { of as observableOf } from 'rxjs'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { LocationStub } from '../../shared/testing/location.stub'; import { By } from '@angular/platform-browser'; -import { ActivatedRouteStub } from 'src/app/shared/testing/active-router.stub'; -import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils'; -import { WorkspaceItem } from 'src/app/core/submission/models/workspaceitem.model'; -import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; describe('WorkspaceitemsDeletePageComponent', () => { let component: WorkspaceItemsDeletePageComponent; diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts index cff5fd6994c..77ed0519d6b 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.ts @@ -1,17 +1,17 @@ -import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; -import { NoContent } from './../../core/shared/NoContent.model'; -import { RouteService } from 'src/app/core/services/route.service'; -import { getFirstCompletedRemoteData, getRemoteDataPayload } from './../../core/shared/operators'; -import { RemoteData } from 'src/app/core/data/remote-data'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NoContent } from '../../core/shared/NoContent.model'; +import { RouteService } from '../../core/services/route.service'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; import { Component, OnInit } from '@angular/core'; import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; -import { Observable, map, switchMap, take } from 'rxjs'; +import { map, Observable, switchMap, take } from 'rxjs'; import { ActivatedRoute, Data, Params, Router } from '@angular/router'; import { Location } from '@angular/common'; -import { WorkspaceitemDataService } from 'src/app/core/submission/workspaceitem-data.service'; +import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service'; import { TranslateService } from '@ngx-translate/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; @Component({ selector: 'ds-workspaceitems-delete-page', From 7d5f44205ec673e632738b052acb25ac1db96672 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 3 May 2023 11:11:10 +0200 Subject: [PATCH 043/167] [DURACOM-134] Fixes --- .../workspaceitems-delete-page.component.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts index fc0bef83f0f..c11659df24b 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -25,7 +25,7 @@ describe('WorkspaceitemsDeletePageComponent', () => { let fixture: ComponentFixture; const workspaceitemDataServiceSpy = jasmine.createSpyObj('WorkspaceitemDataService', { - delete: jasmine.createSpy('delete') + delete: observableOf(createSuccessfulRemoteDataObject({})) }); const wsi = new WorkspaceItem(); @@ -88,9 +88,7 @@ describe('WorkspaceitemsDeletePageComponent', () => { }); it('should have the current WorkspaceItem', () => { - console.log( (component as any).activatedRoute, 'data wsi'); (component as any).activatedRoute.data.subscribe((data) => { - console.log(data, 'dataaa'); expect(data.wsi.payload.id).toEqual('1234'); }); }); @@ -103,7 +101,6 @@ describe('WorkspaceitemsDeletePageComponent', () => { }); it('should call workspaceItemService.delete', () => { - spyOn(workspaceitemDataServiceSpy, 'delete').and.returnValue(observableOf(createSuccessfulRemoteDataObject({}))); component.sendDeleteRequest(); expect((component as any).workspaceItemService.delete).toHaveBeenCalledWith('1234'); }); From 39a1f7f196b707d8dd5a019eb6eecf443656bb6e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 27 Jul 2023 12:14:47 +0200 Subject: [PATCH 044/167] [CST-10123] fix issue with infinite RetrieveAuthenticatedEpersonSuccessAction dispatched --- src/app/core/auth/auth.effects.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index e7301d1ae42..2ed01e1eb86 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -137,6 +137,7 @@ export class AuthEffects { user$ = this.authService.retrieveAuthenticatedUserByHref(action.payload); } return user$.pipe( + take(1), map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user)), catchError((error) => observableOf(new RetrieveAuthenticatedEpersonErrorAction(error)))); }) From 996013716bbdcc1865aadb7475c9aeb4e72f642d Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 18 May 2023 19:03:37 +0200 Subject: [PATCH 045/167] Fix test --- .../workspaceitems-delete-page.component.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts index c11659df24b..e2135636a54 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -6,7 +6,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component'; import { ActivatedRoute, Router } from '@angular/router'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; @@ -46,7 +46,10 @@ describe('WorkspaceitemsDeletePageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], + imports: [ + NgbModalModule, + TranslateModule.forRoot() + ], declarations: [WorkspaceItemsDeletePageComponent], providers: [ { @@ -65,7 +68,6 @@ describe('WorkspaceitemsDeletePageComponent', () => { useValue: workspaceitemDataServiceSpy, }, { provide: Location, useValue: new LocationStub() }, - { provide: NgbModal, useValue: modalService }, { provide: NotificationsService, useValue: new NotificationsServiceStub(), From fde497e34f273798aec5f8a301701a92848a123e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Thu, 18 May 2023 13:28:08 -0500 Subject: [PATCH 046/167] Comment out problematic test in "workspaceitems-delete-page" component --- .../workspaceitems-delete-page.component.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts index e2135636a54..c0094e36aa6 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -14,7 +14,6 @@ import { Location } from '@angular/common'; import { of as observableOf } from 'rxjs'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { LocationStub } from '../../shared/testing/location.stub'; -import { By } from '@angular/platform-browser'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; @@ -95,12 +94,12 @@ describe('WorkspaceitemsDeletePageComponent', () => { }); }); - it('should delete the target workspace item', () => { + /*it('should delete the target workspace item', () => { spyOn((component as any).modalService, 'open').and.returnValue({}); component.confirmDelete(By.css('#delete-modal')); fixture.detectChanges(); expect((component as any).modalService.open).toHaveBeenCalled(); - }); + });*/ it('should call workspaceItemService.delete', () => { component.sendDeleteRequest(); From 5a26a320604b94deb13883e481f23cffadaabfaf Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 18 May 2023 23:21:45 +0200 Subject: [PATCH 047/167] Fix test --- .../workspaceitems-delete-page.component.spec.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts index c0094e36aa6..ac2878e8bd8 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.spec.ts @@ -14,6 +14,7 @@ import { Location } from '@angular/common'; import { of as observableOf } from 'rxjs'; import { routeServiceStub } from '../../shared/testing/route-service.stub'; import { LocationStub } from '../../shared/testing/location.stub'; +import { By } from '@angular/platform-browser'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; @@ -39,10 +40,6 @@ describe('WorkspaceitemsDeletePageComponent', () => { onDefaultLangChange: new EventEmitter() }; - const modalService = { - open: () => {/** empty */}, - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -94,12 +91,12 @@ describe('WorkspaceitemsDeletePageComponent', () => { }); }); - /*it('should delete the target workspace item', () => { - spyOn((component as any).modalService, 'open').and.returnValue({}); + it('should delete the target workspace item', () => { + spyOn((component as any).modalService, 'open').and.returnValue({result: Promise.resolve('ok')}); component.confirmDelete(By.css('#delete-modal')); fixture.detectChanges(); expect((component as any).modalService.open).toHaveBeenCalled(); - });*/ + }); it('should call workspaceItemService.delete', () => { component.sendDeleteRequest(); From ae5a22ea7816fb8482dd739e6633eb3977ecc7ce Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 18 May 2023 23:21:45 +0200 Subject: [PATCH 048/167] [DSC-1091] Fix failing tests --- src/app/shared/form/form.component.html | 1 - src/app/shared/form/form.component.spec.ts | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index 29351a7fd81..8fb82659811 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -15,7 +15,6 @@
diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html index 477f96e19e2..09594bd56ac 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html @@ -1,6 +1,6 @@ - From 59cb63a32efee2209797e6efaba14c94d38d48e5 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Thu, 7 Sep 2023 10:03:36 +0300 Subject: [PATCH 062/167] [DSC-1235] adjust display of cris-layout-metrics-box --- .../boxes/metrics/cris-layout-metrics-box.component.html | 2 +- .../boxes/metrics/metric-row/metric-row.component.html | 2 +- .../shared/metric/metric-loader/metric-loader.component.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/cris-layout-metrics-box.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/cris-layout-metrics-box.component.html index d1176864adf..fc6fabc57a7 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/cris-layout-metrics-box.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/cris-layout-metrics-box.component.html @@ -4,7 +4,7 @@
+ class="d-flex flex-wrap gap-3">
diff --git a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html index 09594bd56ac..50584b04c41 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html +++ b/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metrics/metric-row/metric-row.component.html @@ -1,6 +1,6 @@ - diff --git a/src/app/shared/metric/metric-loader/metric-loader.component.html b/src/app/shared/metric/metric-loader/metric-loader.component.html index f8f338c4545..4d3eb179215 100644 --- a/src/app/shared/metric/metric-loader/metric-loader.component.html +++ b/src/app/shared/metric/metric-loader/metric-loader.component.html @@ -1,3 +1,3 @@ - From f6d7b0d2b2a8cd4b60f235f087e21533de2df53a Mon Sep 17 00:00:00 2001 From: Nikita Krivonosov Date: Fri, 8 Sep 2023 09:17:46 +0200 Subject: [PATCH 065/167] [CST-11736] Submission form: Wrong mapping between validation error and form model of a group --- src/app/shared/form/form.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 05f88c095eb..34d2d2266d6 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -203,10 +203,10 @@ export class FormComponent implements OnDestroy, OnInit { } if (field) { - const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel); + const modelArrayIndex = fieldIndex > 0 ? fieldIndex : null; + const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel, modelArrayIndex); this.formService.addErrorToField(field, model, error.message); this.changeDetectorRef.detectChanges(); - } }); From 28527c24a48e2ead45c21734ce81ac5c403091a3 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 8 Sep 2023 16:08:16 +0200 Subject: [PATCH 066/167] [DSC-1224] Fixes failing tests --- .../metadata-registry.component.spec.ts | 13 ++++++++-- .../metadata-registry.component.ts | 2 -- .../metadata-schema-export.service.spec.ts | 24 +++++++++++++++++-- .../metadata-schema-export.service.ts | 6 ++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts index 944288a7a51..36ecdaf3162 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts @@ -1,6 +1,6 @@ import { MetadataRegistryComponent } from './metadata-registry.component'; import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; -import { of as observableOf } from 'rxjs'; +import { of, of as observableOf } from 'rxjs'; import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; @@ -20,6 +20,9 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { + MetadataSchemaExportService +} from '../../../shared/metadata-export/metadata-schema-export/metadata-schema-export.service'; describe('MetadataRegistryComponent', () => { let comp: MetadataRegistryComponent; @@ -75,7 +78,13 @@ describe('MetadataRegistryComponent', () => { { provide: RegistryService, useValue: registryServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: PaginationService, useValue: paginationService }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() } + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { + provide: MetadataSchemaExportService, + useValue: jasmine.createSpyObj('metadataSchemaExportService', { + exportSchema: of(1), + }) + } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(MetadataRegistryComponent, { diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts index 723338ff523..0f052ba25ef 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -7,7 +7,6 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { filter, map, switchMap, take } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; @@ -49,7 +48,6 @@ export class MetadataRegistryComponent { constructor(private registryService: RegistryService, private notificationsService: NotificationsService, - private router: Router, private paginationService: PaginationService, private translateService: TranslateService, private readonly metadataSchemaExportService: MetadataSchemaExportService) { diff --git a/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.spec.ts b/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.spec.ts index 9b3a5b96f50..cca41263ab5 100644 --- a/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.spec.ts +++ b/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.spec.ts @@ -1,16 +1,36 @@ import { TestBed } from '@angular/core/testing'; import { MetadataSchemaExportService } from './metadata-schema-export.service'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { ScriptDataService } from '../../../core/data/processes/script-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; describe('MetadataSchemaExportService', () => { let service: MetadataSchemaExportService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], + providers: [ + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { + provide: ScriptDataService, + useValue: jasmine.createSpyObj('scriptDataService', { + invoke: createSuccessfulRemoteDataObject$({ processId: '45' }), + }) + } + ], + }); service = TestBed.inject(MetadataSchemaExportService); }); it('should be created', () => { expect(service).toBeTruthy(); }); -}); +}) +; diff --git a/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.ts b/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.ts index 674b82e80e0..9190ddb86a3 100644 --- a/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.ts +++ b/src/app/shared/metadata-export/metadata-schema-export/metadata-schema-export.service.ts @@ -18,9 +18,9 @@ export class MetadataSchemaExportService { private readonly SCRIPT_NAME = 'export-schema'; constructor( - protected notificationsService: NotificationsService, - protected translate: TranslateService, - protected scriptDataService: ScriptDataService + protected readonly notificationsService: NotificationsService, + protected readonly translate: TranslateService, + protected readonly scriptDataService: ScriptDataService ) { } From f8abdb8561e6112942c81d817228698724f4b04c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 11 Sep 2023 09:37:30 +0200 Subject: [PATCH 067/167] [DSC-1224] Use button for registry download --- .../metadata-registry.component.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html index dc0fc48401c..fde93042e09 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html @@ -44,11 +44,13 @@