From 48ad0fc8ae67f0d0aae5d586efadb52aa3316f04 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 31 Dec 2021 11:03:47 +0530 Subject: [PATCH 01/56] [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 02/56] [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 03/56] [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 04/56] [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 05/56] [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 06/56] [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 07/56] [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 08/56] [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 09/56] [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 aa8af1b84d7e26d1a480b470c1eacc731a5970a1 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 3 Aug 2023 13:57:56 +0200 Subject: [PATCH 10/56] [CST-11168] changed labels for access conditions options --- .../accesses/section-accesses.component.ts | 3 ++- .../accesses/section-accesses.model.ts | 2 ++ .../section-upload-file-edit.component.ts | 5 ++++- .../edit/section-upload-file-edit.model.ts | 2 ++ src/assets/i18n/en.json5 | 22 +++++++++++++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/accesses/section-accesses.component.ts b/src/app/submission/sections/accesses/section-accesses.component.ts index adff36e9a4f..8529d62efb2 100644 --- a/src/app/submission/sections/accesses/section-accesses.component.ts +++ b/src/app/submission/sections/accesses/section-accesses.component.ts @@ -32,6 +32,7 @@ import { ACCESS_CONDITION_GROUP_LAYOUT, ACCESS_CONDITIONS_FORM_ARRAY_CONFIG, ACCESS_CONDITIONS_FORM_ARRAY_LAYOUT, + ACCESS_CONDITIONS_FORM_TRANSLATION_CONFIG, ACCESS_FORM_CHECKBOX_CONFIG, ACCESS_FORM_CHECKBOX_LAYOUT, FORM_ACCESS_CONDITION_END_DATE_CONFIG, @@ -336,7 +337,7 @@ export class SubmissionSectionAccessesComponent extends SectionModelComponent { for (const accessCondition of this.availableAccessConditionOptions) { accessConditionTypeOptions.push( { - label: accessCondition.name, + label: this.translate.instant(`${ACCESS_CONDITIONS_FORM_TRANSLATION_CONFIG}${accessCondition.name}`), value: accessCondition.name } ); diff --git a/src/app/submission/sections/accesses/section-accesses.model.ts b/src/app/submission/sections/accesses/section-accesses.model.ts index 435c521175a..3146e0351ab 100644 --- a/src/app/submission/sections/accesses/section-accesses.model.ts +++ b/src/app/submission/sections/accesses/section-accesses.model.ts @@ -121,3 +121,5 @@ export const FORM_ACCESS_CONDITION_END_DATE_LAYOUT: DynamicFormControlLayout = { host: 'col-6' } }; + +export const ACCESS_CONDITIONS_FORM_TRANSLATION_CONFIG = 'access.condition.value.'; diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index b57cc6b7ac9..d13e54c6333 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -29,6 +29,7 @@ import { BITSTREAM_FORM_ACCESS_CONDITION_START_DATE_LAYOUT, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_CONFIG, BITSTREAM_FORM_ACCESS_CONDITION_TYPE_LAYOUT, + BITSTREAM_FORM_ACCESS_CONDITIONS_TRANSLATION_CONFIG, BITSTREAM_METADATA_FORM_GROUP_CONFIG, BITSTREAM_METADATA_FORM_GROUP_LAYOUT } from './section-upload-file-edit.model'; @@ -59,6 +60,7 @@ import { Subscription } from 'rxjs'; import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/misc/dynamic-form-control-relation.model'; import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model'; import { DynamicSelectModelConfig } from '@ng-dynamic-forms/core/lib/model/select/dynamic-select.model'; +import { TranslateService } from '@ngx-translate/core'; /** * This component represents the edit form for bitstream @@ -188,6 +190,7 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { private operationsBuilder: JsonPatchOperationsBuilder, private operationsService: SubmissionJsonPatchOperationsService, private uploadService: SectionUploadService, + private translate: TranslateService, ) { } @@ -315,7 +318,7 @@ export class SubmissionSectionUploadFileEditComponent implements OnInit { for (const accessCondition of this.availableAccessConditionOptions) { accessConditionTypeOptions.push( { - label: accessCondition.name, + label: this.translate.instant(`${BITSTREAM_FORM_ACCESS_CONDITIONS_TRANSLATION_CONFIG}${accessCondition.name}`), value: accessCondition.name } ); diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts index 26b27afb643..c8eb69d591f 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.model.ts @@ -118,3 +118,5 @@ export const BITSTREAM_FORM_ACCESS_CONDITION_END_DATE_LAYOUT: DynamicFormControl host: 'col-6' } }; + +export const BITSTREAM_FORM_ACCESS_CONDITIONS_TRANSLATION_CONFIG = 'access.condition.value.'; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ca1ee2fd1c8..bb565acc704 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6368,6 +6368,28 @@ "submission.sections.accesses.form.until-placeholder": "Until", + "access.condition.value.openaccess": "Open Access", + + "access.condition.value.embargo": "Embargo", + + "access.condition.value.vlan48": "ITG Subnet", + + "access.condition.value.vlan-campus": "IAS Campus Network", + + "access.condition.value.ldap": "IAS Users", + + "access.condition.value.staff": "Staff Only", + + "access.condition.value.faculty": "Faculty Only", + + "access.condition.value.member": "Members Only", + + "access.condition.value.administrator": "Administrator Only", + + "access.condition.value.embargoed": "Embargoed", + + "access.condition.value.lease": "Lease", + "submission.sections.license.granted-label": "I confirm the license above", "submission.sections.license.required": "You must accept the license", From 53abf51c20091aa0c9e9d39dd7450ad3626c831b Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 20 Sep 2023 16:26:12 +0200 Subject: [PATCH 11/56] [DSC-1251] consider empty value of configuration key request.item.type --- .../attachment-render.component.spec.ts | 14 ++++++++++++- .../file-download-button.component.html | 9 +++++--- .../file-download-button.component.spec.ts | 17 +++++++++++++++ .../file-download-link.component.spec.ts | 11 ++++++++++ .../file-download-link.component.ts | 21 +++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) 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-render.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/attachment-render.component.spec.ts index 541606ccdde..dbe7ec1e011 100644 --- a/src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/advanced-attachment/bitstream-attachment/attachment-render/attachment-render.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/attachment-render.component.spec.ts @@ -6,16 +6,28 @@ import { AuthorizationDataService } from '../../../../../../../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataServiceStub } from '../../../../../../../../../shared/testing/authorization-service.stub'; +import { ConfigurationDataService } from '../../../../../../../../../core/data/configuration-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../../../../../../../../../shared/remote-data.utils'; +import { ConfigurationProperty } from '../../../../../../../../../core/shared/configuration-property.model'; describe('AttachmentRenderComponent', () => { let component: AttachmentRenderComponent; let fixture: ComponentFixture; + let configurationDataService: ConfigurationDataService; beforeEach(async () => { + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'request.item.type', + values: [] + })) + }); + await TestBed.configureTestingModule({ declarations: [ AttachmentRenderComponent ], providers: [ - {provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub} + {provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub}, + {provide: ConfigurationDataService, useValue: configurationDataService} ], schemas: [ NO_ERRORS_SCHEMA ] }) diff --git a/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 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 index a04507c87cb..d49b6fabcc8 100644 --- a/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 +++ 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 @@ -9,9 +9,12 @@ - {{ 'cris-layout.advanced-attachment.requestACopy' | - translate }} + diff --git a/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 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 index b6883e74233..d1d6d86eac9 100644 --- a/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 +++ 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 @@ -10,6 +10,9 @@ import { TranslateLoaderMock } from '../../../../../../../../../../../shared/moc import { SharedModule } from '../../../../../../../../../../../shared/shared.module'; import { FileDownloadButtonComponent } from './file-download-button.component'; +import { ConfigurationDataService } from '../../../../../../../../../../../core/data/configuration-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../../../../../../../../../../../shared/remote-data.utils'; +import { ConfigurationProperty } from '../../../../../../../../../../../core/shared/configuration-property.model'; describe('FileDownloadButtonComponent', () => { let component: FileDownloadButtonComponent; @@ -19,6 +22,7 @@ describe('FileDownloadButtonComponent', () => { let bitstream: Bitstream; let item: Item; + let configurationDataService: ConfigurationDataService; function init() { authorizationService = jasmine.createSpyObj('authorizationService', { @@ -36,6 +40,12 @@ describe('FileDownloadButtonComponent', () => { self: { href: 'obj-selflink' } } }); + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'request.item.type', + values: [] + })) + }); } @@ -56,6 +66,7 @@ describe('FileDownloadButtonComponent', () => { declarations: [FileDownloadButtonComponent], providers: [ { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: ConfigurationDataService, useValue: configurationDataService } ] }) .compileComponents(); @@ -91,4 +102,10 @@ describe('FileDownloadButtonComponent', () => { expect(fixture.debugElement.query(By.css('[data-test="requestACopy"]'))).toBeTruthy(); }); + it('should show a disabled can request a copy button when request.item.type has no value', () => { + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(of(false)); + component.ngOnInit(); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-test="requestACopy"]')).nativeElement.disabled).toBeTruthy(); + }); }); diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 5633c94fe8c..8d22691bd5f 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -10,6 +10,9 @@ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { Item } from '../../core/shared/item.model'; import { getItemModuleRoute } from '../../item-page/item-page-routing-paths'; import { RouterLinkDirectiveStub } from '../testing/router-link-directive.stub'; +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; describe('FileDownloadLinkComponent', () => { let component: FileDownloadLinkComponent; @@ -20,6 +23,7 @@ describe('FileDownloadLinkComponent', () => { let bitstream: Bitstream; let item: Item; + let configurationDataService: ConfigurationDataService; function init() { authorizationService = jasmine.createSpyObj('authorizationService', { @@ -37,6 +41,12 @@ describe('FileDownloadLinkComponent', () => { self: {href: 'obj-selflink'} } }); + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { + name: 'request.item.type', + values: [] + })) + }); } function initTestbed() { @@ -44,6 +54,7 @@ describe('FileDownloadLinkComponent', () => { declarations: [FileDownloadLinkComponent, RouterLinkDirectiveStub], providers: [ {provide: AuthorizationDataService, useValue: authorizationService}, + {provide: ConfigurationDataService, useValue: configurationDataService} ] }) .compileComponents(); diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index 22dcd120274..d2f1d215c0d 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -7,6 +7,9 @@ import { hasValue, isNotEmpty } from '../empty.util'; import { map } from 'rxjs/operators'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { Item } from '../../core/shared/item.model'; +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from 'src/app/core/shared/operators'; +import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; @Component({ selector: 'ds-file-download-link', @@ -48,8 +51,16 @@ export class FileDownloadLinkComponent implements OnInit { canDownload$: Observable; + /** + * Whether or not the user can request a copy of the item + * based on the configuration property `request.item.type`. + */ + public canRequestItemCopy$: Observable; + + constructor( private authorizationService: AuthorizationDataService, + private configurationService: ConfigurationDataService, ) { } @@ -60,9 +71,19 @@ export class FileDownloadLinkComponent implements OnInit { this.bitstreamPath$ = observableCombineLatest([this.canDownload$, canRequestACopy$]).pipe( map(([canDownload, canRequestACopy]) => this.getBitstreamPath(canDownload, canRequestACopy)) ); + + this.canRequestItemCopy$ = this.configurationService.findByPropertyName('request.item.type').pipe( + getFirstCompletedRemoteData(), + getRemoteDataPayload(), + map((requestItemType: ConfigurationProperty) => + // in case requestItemType empty/commented out(undefined) - request-copy not allowed + hasValue(requestItemType) && requestItemType.values.length > 0 + ), + ); } else { this.bitstreamPath$ = observableOf(this.getBitstreamDownloadPath()); this.canDownload$ = observableOf(true); + this.canRequestItemCopy$ = observableOf(false); } } From e82bfadaa7f95c44f412cba0bb015b51a67a79df Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 20 Sep 2023 17:26:40 +0200 Subject: [PATCH 12/56] [DSC-1252] display visibility switch for input groups --- src/app/shared/form/builder/parsers/field-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index fc83883b6c5..15ce3068374 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -344,7 +344,7 @@ export abstract class FieldParser { (controlModel as DsDynamicInputModel).typeBindRelations = this.getTypeBindRelations(this.configData.typeBind, this.parserOptions.typeField); } - controlModel.securityConfigLevel = this.mapBetweenMetadataRowAndSecurityMetadataLevels(this.fieldId); + controlModel.securityConfigLevel = this.mapBetweenMetadataRowAndSecurityMetadataLevels(this.getFieldId()); return controlModel; } From 5d704c610d8f009b3e5e0ff0efcc1e478bc6bb43 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 25 Sep 2023 18:10:26 +0200 Subject: [PATCH 13/56] [CST-10703] initial commit (views & unit tests) --- src/app/app-routing.module.ts | 5 + src/app/core/core.module.ts | 4 +- .../core/data/eperson-registration.service.ts | 55 ++++++++++- .../external-login-page-routing.module.ts | 25 +++++ .../external-login-page.component.html | 3 + .../external-login-page.component.scss | 0 .../external-login-page.component.spec.ts | 25 +++++ .../external-login-page.component.ts | 43 +++++++++ .../external-login-page.module.ts | 24 +++++ .../themed-external-login-page.component.ts | 25 +++++ .../confirm-email.component.html | 37 ++++++++ .../confirm-email.component.scss | 0 .../confirm-email.component.spec.ts | 25 +++++ .../confirm-email/confirm-email.component.ts | 41 +++++++++ .../confirmation-sent.component.html | 5 + .../confirmation-sent.component.scss | 0 .../confirmation-sent.component.spec.ts | 55 +++++++++++ .../confirmation-sent.component.ts | 9 ++ .../email-validated.component.html | 9 ++ .../email-validated.component.scss | 0 .../email-validated.component.spec.ts | 79 ++++++++++++++++ .../email-validated.component.ts | 11 +++ .../provide-email.component.html | 38 ++++++++ .../provide-email.component.scss | 0 .../provide-email.component.spec.ts | 43 +++++++++ .../provide-email/provide-email.component.ts | 47 ++++++++++ .../external-log-in.methods-decorator.ts | 29 ++++++ .../external-log-in.component.html | 29 ++++++ .../external-log-in.component.scss | 0 .../external-log-in.component.spec.ts | 89 ++++++++++++++++++ .../external-log-in.component.ts | 91 +++++++++++++++++++ .../external-login-method-entry.component.ts | 22 +++++ .../models/registration-data.mock.model.ts | 31 +++++++ .../models/registration-data.model.ts | 70 ++++++++++++++ .../models/registration-data.resource-type.ts | 9 ++ .../orcid-confirmation.component.html | 35 +++++++ .../orcid-confirmation.component.scss | 0 .../orcid-confirmation.component.spec.ts | 74 +++++++++++++++ .../orcid-confirmation.component.ts | 60 ++++++++++++ .../container/log-in-container.component.html | 1 - src/app/shared/log-in/log-in.component.html | 8 +- src/app/shared/log-in/log-in.component.ts | 11 ++- src/app/shared/shared.module.ts | 16 +++- src/assets/i18n/en.json5 | 28 +++++- 44 files changed, 1198 insertions(+), 13 deletions(-) create mode 100644 src/app/external-login-page/external-login-page-routing.module.ts create mode 100644 src/app/external-login-page/external-login-page.component.html create mode 100644 src/app/external-login-page/external-login-page.component.scss create mode 100644 src/app/external-login-page/external-login-page.component.spec.ts create mode 100644 src/app/external-login-page/external-login-page.component.ts create mode 100644 src/app/external-login-page/external-login-page.module.ts create mode 100644 src/app/external-login-page/themed-external-login-page.component.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts create mode 100644 src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts create mode 100644 src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html create mode 100644 src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.scss create mode 100644 src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts create mode 100644 src/app/shared/external-log-in-complete/external-login-method-entry.component.ts create mode 100644 src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts create mode 100644 src/app/shared/external-log-in-complete/models/registration-data.model.ts create mode 100644 src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts create mode 100644 src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html create mode 100644 src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss create mode 100644 src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts create mode 100644 src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d585d737437..82af5149d06 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -171,6 +171,11 @@ import { RedirectService } from './redirect/redirect.service'; loadChildren: () => import('./login-page/login-page.module') .then((m) => m.LoginPageModule) }, + { + path: 'external-login', + loadChildren: () => import('./external-login-page/external-login-page.module') + .then((m) => m.ExternalLoginPageModule) + }, { path: 'logout', loadChildren: () => import('./logout-page/logout-page.module') diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f57eb72e9c5..d22aadc8a55 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -232,6 +232,7 @@ import { import { ProductDatasetSchemaType } from './metadata/schema-json-ld/schema-types/product/product-dataset-schema-type'; import { PersonSchemaType } from './metadata/schema-json-ld/schema-types/Person/person-schema-type'; import {ItemRequest} from './shared/item-request.model'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -470,7 +471,8 @@ export const models = WorkflowOwnerStatistics, LoginStatistics, Metric, - ItemRequest + ItemRequest, + RegistrationData ]; @NgModule({ diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 1ec6f7eb299..e1b77977d6a 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { GetRequest, PostRequest } from './request.models'; +import { GetRequest, PatchRequest, PostRequest } from './request.models'; import { Observable } from 'rxjs'; import { filter, find, map } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -15,14 +15,14 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { HttpHeaders } from '@angular/common/http'; import { HttpParams } from '@angular/common/http'; - +import { Operation } from 'fast-json-patch'; @Injectable({ providedIn: 'root', }) /** * Service that will register a new email address and request a token */ -export class EpersonRegistrationService { +export class EpersonRegistrationService{ protected linkPath = 'registrations'; protected searchByTokenPath = '/search/findByToken?token='; @@ -32,7 +32,6 @@ export class EpersonRegistrationService { protected rdbService: RemoteDataBuildService, protected halService: HALEndpointService, ) { - } /** @@ -142,4 +141,52 @@ export class EpersonRegistrationService { }); return this.rdbService.buildSingle(href$); } + + /** + * Patch the registration object to update the email address + * @param value provided by the user during the registration confirmation process + * @param registrationId The id of the registration object + * @param token The token of the registration object + * @param updateValue Flag to indicate if the email should be updated or added + * @returns Remote Data state of the patch request + */ + patchUpdateRegistration(value: string, field: string, registrationId: string, token: string, updateValue: boolean) { + const requestId = this.requestService.generateRequestId(); + + const href$ = this.getRegistrationEndpoint().pipe( + find((href: string) => hasValue(href)), + map((href: string) => `${href}/${registrationId}?token=${token}`), + ); + + href$.subscribe((href: string) => { + const operations = this.generateOperations(value, field, updateValue); + const patchRequest = new PatchRequest(requestId, href, operations); + this.requestService.send(patchRequest); + }); + + return this.rdbService.buildFromRequestUUID(requestId); + } + + /** + * Custom method to generate the operations to be performed on the registration object + * @param value provided by the user during the registration confirmation process + * @param updateValue Flag to indicate if the email should be updated or added + * @returns Operations to be performed on the registration object + */ + private generateOperations(value: string, field: string, updateValue: boolean): Operation[] { + let operations = []; + if (hasValue(value) && updateValue) { + operations = [...operations, { + op: 'replace', path: `/${field}`, value: value + }]; + } + + if (hasValue(value) && !updateValue) { + operations = [...operations, { + op: 'add', path: `/${field}`, value: value + }]; + } + + return operations; + } } diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts new file mode 100644 index 00000000000..4e64d267de8 --- /dev/null +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; +import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + component: ThemedExternalLoginPageComponent, + // resolve: { breadcrumb: I18nBreadcrumbResolver }, + // data: { breadcrumbKey: 'external-login', title: 'login.title' }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + I18nBreadcrumbResolver, + I18nBreadcrumbsService + ] +}) +export class ExternalLoginPageRoutingModule { } diff --git a/src/app/external-login-page/external-login-page.component.html b/src/app/external-login-page/external-login-page.component.html new file mode 100644 index 00000000000..d2daa24a561 --- /dev/null +++ b/src/app/external-login-page/external-login-page.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/external-login-page/external-login-page.component.scss b/src/app/external-login-page/external-login-page.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/external-login-page/external-login-page.component.spec.ts b/src/app/external-login-page/external-login-page.component.spec.ts new file mode 100644 index 00000000000..56f0d81d189 --- /dev/null +++ b/src/app/external-login-page/external-login-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalLoginPageComponent } from './external-login-page.component'; + +describe('ExternalLoginPageComponent', () => { + let component: ExternalLoginPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExternalLoginPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalLoginPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts new file mode 100644 index 00000000000..94ab4424b86 --- /dev/null +++ b/src/app/external-login-page/external-login-page.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { hasValue } from '../shared/empty.util'; +import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { RemoteData } from '../core/data/remote-data'; +import { Registration } from '../core/shared/registration.model'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; + +@Component({ + templateUrl: './external-login-page.component.html', + styleUrls: ['./external-login-page.component.scss'] +}) +export class ExternalLoginPageComponent implements OnInit { + + public token: string; + + public registrationData: RegistrationData = mockRegistrationDataModel; + + constructor( + private epersonRegistrationService: EpersonRegistrationService, + private router: Router, + ) { + this.token = this.router.parseUrl(this.router.url).queryParams.token; + } + + ngOnInit(): void { + // TODO: call the method getTokenSearchEndpoint (eperson-registration.service.ts ) protected searchByTokenPath = '/search/findByToken?token='; + // token will be provided by the url (returned by REST API) + console.log('ExternalLoginPageComponent ngOnInit'); + if (hasValue(this.token)) { + this.epersonRegistrationService.searchByToken(this.token).subscribe((registration: RemoteData + ) => { + console.log('ExternalLoginPageComponent ngOnInit registration', registration); + if (registration.hasSucceeded) { + this.registrationData = Object.assign(new RegistrationData(), registration.payload); + console.log('ExternalLoginPageComponent ngOnInit registrationData', this.registrationData); + } + }); + } + } + +} diff --git a/src/app/external-login-page/external-login-page.module.ts b/src/app/external-login-page/external-login-page.module.ts new file mode 100644 index 00000000000..697ae7e1a8e --- /dev/null +++ b/src/app/external-login-page/external-login-page.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ExternalLoginPageRoutingModule } from './external-login-page-routing.module'; +import { ExternalLoginPageComponent } from './external-login-page.component'; +import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; +import { SharedModule } from '../shared/shared.module'; + +const COMPONENTS = [ + ExternalLoginPageComponent, + ThemedExternalLoginPageComponent, +]; + +@NgModule({ + declarations: [ + ...COMPONENTS + ], + imports: [ + CommonModule, + ExternalLoginPageRoutingModule, + SharedModule + ] +}) +export class ExternalLoginPageModule { } diff --git a/src/app/external-login-page/themed-external-login-page.component.ts b/src/app/external-login-page/themed-external-login-page.component.ts new file mode 100644 index 00000000000..975b9a2f55e --- /dev/null +++ b/src/app/external-login-page/themed-external-login-page.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../shared/theme-support/themed.component'; +import { ExternalLoginPageComponent } from './external-login-page.component'; + +/** + * Themed wrapper for ExternalLoginPageComponent + */ +@Component({ + selector: 'ds-themed-external-login-page', + styleUrls: [], + templateUrl: './../shared/theme-support/themed.component.html' +}) +export class ThemedExternalLoginPageComponent extends ThemedComponent { + protected getComponentName(): string { + return 'ExternalLoginPageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../themes/${themeName}/app/external-login-page/external-login-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./external-login-page.component`); + } +} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html new file mode 100644 index 00000000000..97d9869f411 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html @@ -0,0 +1,37 @@ +

+ {{ "external-login.confirm-email.header" | translate }} +

+ +
+
+ +
+ {{ "external-login.confirmation.email-required" | translate }} +
+
+ {{ "external-login.confirmation.email-invalid" | translate }} +
+
+ + +
diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts new file mode 100644 index 00000000000..8e426f27fc0 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmEmailComponent } from './confirm-email.component'; + +describe('ConfirmEmailComponent', () => { + let component: ConfirmEmailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ConfirmEmailComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmEmailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts new file mode 100644 index 00000000000..0b177db851d --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -0,0 +1,41 @@ +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; +import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; + +@Component({ + selector: 'ds-confirm-email', + templateUrl: './confirm-email.component.html', + styleUrls: ['./confirm-email.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConfirmEmailComponent { + + emailForm: FormGroup; + + @Input() registrationId: string; + + @Input() token: string; + + constructor( + private formBuilder: FormBuilder, + private epersonRegistrationService: EpersonRegistrationService, + ) { + this.emailForm = this.formBuilder.group({ + email: ['', [Validators.required, Validators.email]] + }); + } + + submitForm() { + this.emailForm.markAllAsTouched(); + if (this.emailForm.valid) { + const email = this.emailForm.get('email').value; + console.log('Email submitted:', email); + this.epersonRegistrationService.patchUpdateRegistration(email, 'email', this.registrationId, this.token, true).pipe( + getFirstCompletedRemoteData() + ).subscribe((update) => { + console.log('Email update:', update); + }); + } + } +} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html new file mode 100644 index 00000000000..f81ebf1a170 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html @@ -0,0 +1,5 @@ +

+ {{ "external-login.confirm-email-sent.header" | translate }} +

+ +

diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts new file mode 100644 index 00000000000..5106220db62 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts @@ -0,0 +1,55 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmationSentComponent } from './confirmation-sent.component'; +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { TranslateService, TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; + +describe('ConfirmationSentComponent', () => { + let component: ConfirmationSentComponent; + let fixture: ComponentFixture; + let compiledTemplate: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ConfirmationSentComponent ], + providers: [ + { provide: TranslateService, useClass: {} }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfirmationSentComponent); + component = fixture.componentInstance; + compiledTemplate = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render translated header', () => { + const headerElement = compiledTemplate.querySelector('h4'); + expect(headerElement.textContent).toContain('Mocked Header Translation'); + }); + + it('should render translated info paragraph', () => { + const infoParagraphElement = compiledTemplate.querySelector('p'); + expect(infoParagraphElement.innerHTML).toContain('Mocked Info Translation'); + }); + +}); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts new file mode 100644 index 00000000000..78a0ef81fed --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts @@ -0,0 +1,9 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'ds-confirmation-sent', + templateUrl: './confirmation-sent.component.html', + styleUrls: ['./confirmation-sent.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConfirmationSentComponent { } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html new file mode 100644 index 00000000000..3fa31b0bd53 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html @@ -0,0 +1,9 @@ +

+ {{ "external-login.validated-email.header" | translate }} +

+ +

+ +
+ +
diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts new file mode 100644 index 00000000000..e961e6fb913 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts @@ -0,0 +1,79 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EmailValidatedComponent } from './email-validated.component'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { BehaviorSubject } from 'rxjs'; +import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; + +describe('EmailValidatedComponent', () => { + let component: EmailValidatedComponent; + let fixture: ComponentFixture; + let compiledTemplate: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ EmailValidatedComponent ], + providers: [ + { provide: TranslateService, useValue: getMockTranslateService() }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EmailValidatedComponent); + component = fixture.componentInstance; + compiledTemplate = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render translated header', () => { + const headerElement = compiledTemplate.querySelector('h4'); + expect(headerElement.textContent).toContain('Mocked Header Translation'); + }); + + it('should render translated info paragraph', () => { + const infoParagraphElement = compiledTemplate.querySelector('p'); + expect(infoParagraphElement.innerHTML).toContain('Mocked Info Translation'); + }); + + it('should render ds-log-in component', () => { + const dsLogInComponent = compiledTemplate.querySelector('ds-log-in'); + expect(dsLogInComponent).toBeTruthy(); + }); + +}); + +// Mock the TranslateService +class MockTranslateService { + private translationSubject = new BehaviorSubject({}); + + get(key: string) { + const translations = { + 'external-login.validated-email.header': 'Mocked Header Translation', + 'external-login.validated-email.info': 'Mocked Info Translation', + }; + + this.translationSubject.next(translations); + + // Return an Observable that mimics TranslateService's behavior + return this.translationSubject.asObservable(); + } +} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts new file mode 100644 index 00000000000..c74cbcfd489 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts @@ -0,0 +1,11 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; + +@Component({ + selector: 'ds-email-validated', + templateUrl: './email-validated.component.html', + styleUrls: ['./email-validated.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class EmailValidatedComponent { + +} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html new file mode 100644 index 00000000000..72c8eb7c88c --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html @@ -0,0 +1,38 @@ +

+ {{ "external-login.provide-email.header" | translate }} +

+ +
+
+ + +
+ {{ "external-login.confirmation.email-required" | translate }} +
+
+ {{ "external-login.confirmation.email-invalid" | translate }} +
+
+ + +
diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts new file mode 100644 index 00000000000..03e28c687d3 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts @@ -0,0 +1,43 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProvideEmailComponent } from './provide-email.component'; +import { FormBuilder } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; + +describe('ProvideEmailComponent', () => { + let component: ProvideEmailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProvideEmailComponent ], + providers: [ + FormBuilder, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProvideEmailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts new file mode 100644 index 00000000000..1d504c869f4 --- /dev/null +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -0,0 +1,47 @@ +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; + +@Component({ + selector: 'ds-provide-email', + templateUrl: './provide-email.component.html', + styleUrls: ['./provide-email.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProvideEmailComponent { + emailForm: FormGroup; + + @Input() registrationId: string; + + @Input() token: string; + + constructor( + private formBuilder: FormBuilder, + private epersonRegistrationService: EpersonRegistrationService + ) { + this.emailForm = this.formBuilder.group({ + email: ['', [Validators.required, Validators.email]], + }); + } + + submitForm() { + this.emailForm.markAllAsTouched(); + if (this.emailForm.valid) { + const email = this.emailForm.get('email').value; + console.log('Email submitted:', email); + this.epersonRegistrationService + .patchUpdateRegistration( + email, + 'email', + this.registrationId, + this.token, + true + ) + .pipe(getFirstCompletedRemoteData()) + .subscribe((update) => { + console.log('Email update:', update); + }); + } + } +} diff --git a/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts b/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts new file mode 100644 index 00000000000..933edf24640 --- /dev/null +++ b/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts @@ -0,0 +1,29 @@ +import { AuthMethodType } from '../../core/auth/models/auth.method-type'; + +/** + * Map to store the external login confirmation component for the given auth method type + */ +const authMethodsMap = new Map(); +/** + * Decorator to register the external login confirmation component for the given auth method type + * @param authMethodType the type of the external login method + */ +export function renderExternalLoginConfirmationFor( + authMethodType: AuthMethodType +) { + return function decorator(objectElement: any) { + if (!objectElement) { + return; + } + authMethodsMap.set(authMethodType, objectElement); + }; +} +/** + * Get the external login confirmation component for the given auth method type + * @param authMethodType the type of the external login method + */ +export function getExternalLoginConfirmationType( + authMethodType: AuthMethodType +) { + return authMethodsMap.get(authMethodType); +} diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html new file mode 100644 index 00000000000..e9b5f27f572 --- /dev/null +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -0,0 +1,29 @@ +
+

{{ 'external-login.confirmation.header' | translate}}

+
+
+ + +
+
+ {{ informationText }} +
+
+
+ + + + + + +
+
+

or

+
+
+ +
+
diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.scss b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts new file mode 100644 index 00000000000..9268ebeb817 --- /dev/null +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts @@ -0,0 +1,89 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalLogInComponent } from './external-log-in.component'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; +import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter, Injector } from '@angular/core'; +import { mockRegistrationDataModel } from '../models/registration-data.mock.model'; +import { By } from '@angular/platform-browser'; +import { of as observableOf } from 'rxjs'; +import { FormBuilder } from '@angular/forms'; + +describe('ExternalLogInComponent', () => { + let component: ExternalLogInComponent; + let fixture: ComponentFixture; + let compiledTemplate: HTMLElement; + const translateServiceStub = { + get: () => observableOf('Mocked Translation Text'), + instant: (key: any) => 'Mocked Translation Text', + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExternalLogInComponent], + providers: [ + { provide: TranslateService, useValue: translateServiceStub }, + { provide: Injector, useValue: {} }, + FormBuilder + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalLogInComponent); + component = fixture.componentInstance; + component.registrationData = mockRegistrationDataModel; + compiledTemplate = fixture.nativeElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set registrationType and informationText correctly when email is present', () => { + expect(component.registrationType).toBe('orcid'); + expect(component.informationText).toContain('orcid'); + }); + + it('should render the template to confirm email when registrationData has email', () => { + const selector = compiledTemplate.querySelector('ds-confirm-email'); + expect(selector).toBeTruthy(); + }); + + it('should render the template with provide email component when registrationData email is null', () => { + component.registrationData.email = null; + fixture.detectChanges(); + const provideEmailComponent = compiledTemplate.querySelector('ds-provide-email'); + expect(provideEmailComponent).toBeTruthy(); + }); + + it('should render the template with log-in component', () => { + const logInComponent = compiledTemplate.querySelector('ds-log-in'); + expect(logInComponent).toBeTruthy(); + }); + + it('should render the template with the translated informationText', () => { + component.informationText = 'Mocked Translation Text'; + fixture.detectChanges(); + const infoText = fixture.debugElement.query(By.css('[data-test="info-text"]')); + expect(infoText.nativeElement.innerHTML).toContain('Mocked Translation Text'); + }); +}); + + diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts new file mode 100644 index 00000000000..091f3750398 --- /dev/null +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input, Injector } from '@angular/core'; +import { getExternalLoginConfirmationType } from '../external-log-in.methods-decorator'; +import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; +import { RegistrationData } from '../models/registration-data.model'; +import { hasValue } from '../../empty.util'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'ds-external-log-in', + templateUrl: './external-log-in.component.html', + styleUrls: ['./external-log-in.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ExternalLogInComponent implements OnInit { + /** + * The type of registration type to be confirmed + */ + registrationType: AuthMethodType = AuthMethodType.Orcid; + /** + * The registration data object + */ + @Input() registrationData: RegistrationData; + /** + * The token to be used to confirm the registration + * @memberof ExternalLogInComponent + */ + @Input() token: string; + /** + * The information text to be displayed, + * depending on the registration type and the presence of an email + * @memberof ExternalLogInComponent + */ + public informationText = ''; + /** + * Injector to inject a registration data to the component with the @Input registrationType + * @type {Injector} + */ + public objectInjector: Injector; + + constructor( + private injector: Injector, + private translate: TranslateService, + ) { + } + + /** + * Provide the registration data object to the objectInjector. + * Generate the information text to be displayed. + */ + ngOnInit(): void { + this.objectInjector = Injector.create({ + providers: [ + { provide: 'registrationDataProvider', useFactory: () => (this.registrationData), deps: [] }, + ], + parent: this.injector + }); + this.registrationType = this.registrationData?.registrationType ?? null; + this.informationText = hasValue(this.registrationData?.email) + ? this.generateInformationTextWhenEmail(this.registrationType) + : this.generateInformationTextWhenNOEmail(this.registrationType); + } + + /** + * Generate the information text to be displayed when the user has no email + * @param authMethod the registration type + */ + private generateInformationTextWhenNOEmail(authMethod: string): string { + if (authMethod) { + const authMethodUppercase = authMethod.toUpperCase(); + return this.translate.instant('external-login.noEmail.informationText', { authMethod: authMethodUppercase }); + } + } + + /** + * Generate the information text to be displayed when the user has an email + * @param authMethod the registration type + */ + private generateInformationTextWhenEmail(authMethod: string): string { + if (authMethod) { + const authMethodUppercase = authMethod.toUpperCase(); + return this.translate.instant('external-login.haveEmail.informationText', { authMethod: authMethodUppercase }); + } + } + + /** + * Get the registration type to be rendered + */ + getExternalLoginConfirmationType() { + return getExternalLoginConfirmationType(this.registrationType); + } +} diff --git a/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts b/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts new file mode 100644 index 00000000000..5d03342e0f4 --- /dev/null +++ b/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts @@ -0,0 +1,22 @@ +import { Component, Inject } from '@angular/core'; +import { RegistrationData } from './models/registration-data.model'; + +/** + * This component renders a form to complete the registration process + */ +@Component({ + template: '' +}) +export abstract class ExternalLoginMethodEntryComponent { + + /** + * The registration data object + */ + public registratioData: RegistrationData; + + constructor( + @Inject('registrationDataProvider') protected injectedRegistrationDataObject: RegistrationData, + ) { + this.registratioData = injectedRegistrationDataObject; + } +} diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts new file mode 100644 index 00000000000..3fdf739e27e --- /dev/null +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -0,0 +1,31 @@ +import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; +import { RegistrationData } from './registration-data.model'; +import { MetadataValue } from 'src/app/core/shared/metadata.models'; + +export const mockRegistrationDataModel: RegistrationData = Object.assign( new RegistrationData(), { + id: '3', + email: 'user@institution.edu', + user: '028dcbb8-0da2-4122-a0ea-254be49ca107', + registrationType: AuthMethodType.Orcid, + netId: '<:orcid>', + registrationMetadata: { + 'eperson.firstname': [ + Object.assign(new MetadataValue(), { + value: 'Power', + language: null, + authority: '', + confidence: -1, + place: -1, + }) + ], + 'eperson.lastname': [ + Object.assign(new MetadataValue(), { + value: 'User', + language: null, + authority: '', + confidence: -1, + place: -1 + }) + ] + } +}); diff --git a/src/app/shared/external-log-in-complete/models/registration-data.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.model.ts new file mode 100644 index 00000000000..4d58c2a13bc --- /dev/null +++ b/src/app/shared/external-log-in-complete/models/registration-data.model.ts @@ -0,0 +1,70 @@ +import { CacheableObject } from '../../../core/cache/cacheable-object.model'; +import { typedObject } from '../../../core/cache/builders/build-decorators'; +import { REGISTRATION_DATA } from './registration-data.resource-type'; +import { autoserialize, deserialize } from 'cerialize'; +import { excludeFromEquals } from '../../../core/utilities/equals.decorators'; +import { ResourceType } from '../../../core/shared/resource-type'; +import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; +import { MetadataMap } from '../../../core/shared/metadata.models'; +import { HALLink } from '../../../core/shared/hal-link.model'; + +/** + * Object that represents the authenticated status of a user + */ +@typedObject +export class RegistrationData implements CacheableObject { + + static type = REGISTRATION_DATA; + + /** + * The unique identifier of this registration data + */ + @autoserialize + id: string; + + /** + * The type for this RegistrationData + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The registered email address + */ + @autoserialize + email: string; + + /** + * The registered user identifier + */ + @autoserialize + user: string; + + /** + * The registration type (e.g. orcid, shibboleth, etc.) + */ + @autoserialize + registrationType?: AuthMethodType; + + /** + * The netId of the user (e.g. for ORCID - <:orcid>) + */ + @autoserialize + netId?: string; + + + /** + * The metadata involved during the registration process + */ + @autoserialize + registrationMetadata?: MetadataMap; + + /** + * The {@link HALLink}s for this RegistrationData + */ + @deserialize + _links: { + self: HALLink; + }; +} diff --git a/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts b/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts new file mode 100644 index 00000000000..2a387c3ca38 --- /dev/null +++ b/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../../core/shared/resource-type'; + +/** + * The resource type for RegistrationData + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const REGISTRATION_DATA = new ResourceType('registration'); diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html new file mode 100644 index 00000000000..4c81c7fdb15 --- /dev/null +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html @@ -0,0 +1,35 @@ + diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts new file mode 100644 index 00000000000..3c856e6f283 --- /dev/null +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts @@ -0,0 +1,74 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OrcidConfirmationComponent } from './orcid-confirmation.component'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { mockRegistrationDataModel } from '../../models/registration-data.mock.model'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; + +describe('OrcidConfirmationComponent', () => { + let component: OrcidConfirmationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + OrcidConfirmationComponent, + BrowserOnlyMockPipe, + ], + providers: [ + FormBuilder, + { provide: 'registrationDataProvider', useValue: mockRegistrationDataModel }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(OrcidConfirmationComponent); + component = fixture.componentInstance; + component.registratioData = mockRegistrationDataModel; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should initialize the form with disabled fields', () => { + expect(component.form).toBeInstanceOf(FormGroup); + expect(component.form.controls.netId.disabled).toBeTrue(); + expect(component.form.controls.firstname.disabled).toBeTrue(); + expect(component.form.controls.lastname.disabled).toBeTrue(); + expect(component.form.controls.email.disabled).toBeTrue(); + }); + + + it('should initialize the form with null email as an empty string', () => { + component.registratioData.email = null; + fixture.detectChanges(); + component.ngOnInit(); + const emailFormControl = component.form.get('email'); + expect(emailFormControl.value).toBe(''); + }); + + it('should not render email input when email is null', () => { + component.registratioData.email = null; + fixture.detectChanges(); + const emailInput = fixture.nativeElement.querySelector('input[type="email"]'); + expect(emailInput).toBeFalsy(); + }); +}); diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts new file mode 100644 index 00000000000..0c93b668ec7 --- /dev/null +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit, ChangeDetectionStrategy, Inject } from '@angular/core'; +import { renderExternalLoginConfirmationFor } from '../../external-log-in.methods-decorator'; +import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { RegistrationData } from '../../models/registration-data.model'; +import { ExternalLoginMethodEntryComponent } from '../../external-login-method-entry.component'; + +@Component({ + selector: 'ds-orcid-confirmation', + templateUrl: './orcid-confirmation.component.html', + styleUrls: ['./orcid-confirmation.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +@renderExternalLoginConfirmationFor(AuthMethodType.Orcid) +export class OrcidConfirmationComponent extends ExternalLoginMethodEntryComponent implements OnInit { + + /** + * The form containing the user's data + */ + public form: FormGroup; + + /** + * @param injectedRegistrationDataObject RegistrationData object provided + * @param formBuilder To build the form + */ + constructor( + @Inject('registrationDataProvider') protected injectedRegistrationDataObject: RegistrationData, + private formBuilder: FormBuilder + ) { + super(injectedRegistrationDataObject); + } + + /** + * Initialize the form with disabled fields + */ + ngOnInit(): void { + this.form = this.formBuilder.group({ + netId: [{ value: this.registratioData.netId, disabled: true }], + firstname: [{ value: this.getFirstname(), disabled: true }], + lastname: [{ value: this.getLastname(), disabled: true }], + email: [{ value: this.registratioData?.email || '', disabled: true }], // email can be null + }); + } + + /** + * Get the firstname of the user from the registration metadata + * @returns the firstname of the user + */ + private getFirstname(): string { + return this.registratioData.registrationMetadata?.['eperson.firstname']?.[0]?.value || ''; + } + + /** + * Get the lastname of the user from the registration metadata + * @returns the lastname of the user + */ + private getLastname(): string { + return this.registratioData.registrationMetadata?.['eperson.lastname']?.[0]?.value || ''; + } +} diff --git a/src/app/shared/log-in/container/log-in-container.component.html b/src/app/shared/log-in/container/log-in-container.component.html index bef6f43b667..3b6ea5d054c 100644 --- a/src/app/shared/log-in/container/log-in-container.component.html +++ b/src/app/shared/log-in/container/log-in-container.component.html @@ -2,4 +2,3 @@ *ngComponentOutlet="getAuthMethodContent(); injector: objectInjector;"> - diff --git a/src/app/shared/log-in/log-in.component.html b/src/app/shared/log-in/log-in.component.html index 36f7034f4d7..11d306c1bc4 100644 --- a/src/app/shared/log-in/log-in.component.html +++ b/src/app/shared/log-in/log-in.component.html @@ -7,7 +7,9 @@ - - {{"login.form.new-user" | translate}} - {{"login.form.forgot-password" | translate}} + + + {{"login.form.new-user" | translate}} + {{"login.form.forgot-password" | translate}} +
diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 94175f0f6c3..1fe595c1ed2 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; +import { Observable, Subscription, take } from 'rxjs'; import { select, Store } from '@ngrx/store'; import uniqBy from 'lodash/uniqBy'; @@ -18,6 +18,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { CoreState } from '../../core/core-state.model'; import { AuthMethodType } from '../../core/auth/models/auth.method-type'; +import de from 'date-fns/esm/locale/de/index.js'; /** * /users/sign-in @@ -36,6 +37,10 @@ export class LogInComponent implements OnInit, OnDestroy { */ @Input() isStandalonePage: boolean; + @Input() excludedAuthMethod: AuthMethodType; + + @Input() showRegisterLink = true; + /** * The list of authentication methods available * @type {AuthMethod[]} @@ -77,6 +82,10 @@ export class LogInComponent implements OnInit, OnDestroy { ).subscribe(methods => { // ignore the ip authentication method when it's returned by the backend this.authMethods = uniqBy(methods.filter(a => a.authMethodType !== AuthMethodType.Ip), 'authMethodType'); + // exclude the given auth method in case there is one + if (hasValue(this.excludedAuthMethod)) { + this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType !== this.excludedAuthMethod); + } }); // set loading diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 4f07cfdeefd..3ad876a114e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -332,6 +332,12 @@ import { EntityIconDirective } from './entity-icon/entity-icon.directive'; import { AdditionalMetadataComponent } from './object-list/search-result-list-element/additional-metadata/additional-metadata.component'; +import { ExternalLogInComponent } from './external-log-in-complete/external-log-in/external-log-in.component'; +import { OrcidConfirmationComponent } from './external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component'; +import { ProvideEmailComponent } from './external-log-in-complete/email-confirmation/provide-email/provide-email.component'; +import { ConfirmEmailComponent } from './external-log-in-complete/email-confirmation/confirm-email/confirm-email.component'; +import { ConfirmationSentComponent } from './external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; +import { EmailValidatedComponent } from './external-log-in-complete/email-confirmation/email-validated/email-validated.component'; const MODULES = [ CommonModule, @@ -469,7 +475,8 @@ const COMPONENTS = [ MetadataLinkViewComponent, ExportExcelSelectorComponent, ThemedBrowseMostElementsComponent, - SearchChartBarHorizontalComponent + SearchChartBarHorizontalComponent, + ExternalLogInComponent, ]; const ENTRY_COMPONENTS = [ @@ -543,7 +550,8 @@ const ENTRY_COMPONENTS = [ ThemedBrowseMostElementsComponent, SearchChartBarHorizontalComponent, RelationshipsListComponent, - AdditionalMetadataComponent + AdditionalMetadataComponent, + OrcidConfirmationComponent, ]; const PROVIDERS = [ @@ -583,6 +591,10 @@ const DIRECTIVES = [ ...COMPONENTS, ...ENTRY_COMPONENTS, ...DIRECTIVES, + ProvideEmailComponent, + ConfirmEmailComponent, + ConfirmationSentComponent, + EmailValidatedComponent, ], providers: [ ...PROVIDERS diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0b4dcd84bd4..faddb3a2b0e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7167,5 +7167,31 @@ "admin.system-wide-alert.breadcrumbs": "System-wide Alerts", - "admin.system-wide-alert.title": "System-wide Alerts" + "admin.system-wide-alert.title": "System-wide Alerts", + + "external-login.confirmation.header": "Information needed to complete the login process", + + "external-login.noEmail.informationText": "The information received from {{authMethod}} are not sufficient to complete the login process. Please provide the missing information below, or login via a different method to associate your {{authMethod}} to an existing account.", + + "external-login.haveEmail.informationText": "It seems that you have not yet an account in this system. If this is the case, please confirm the data received from {{authMethod}} and a new account will be created for you. Otherwise, if you already have an account in the system, please update the email address to match the one already in use in the system or login via a different method to associate your {{authMethod}} to your existing account.", + + "external-login.confirm-email.header": "Confirm or update email", + + "external-login.confirmation.email-required": "Email is required.", + + "external-login.confirmation.email-invalid": "Invalid email format.", + + "external-login.confirm.button.label": "Confirm this email", + + "external-login.confirm-email-sent.header": "Confirmation email sent", + + "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", + + "external-login.validated-email.header": "Email validated", + + "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", + + "external-login.provide-email.header": "Provide email", + + "external-login.provide-email.button.label": "Send Verification link", } From 64598f121ab216a863825181588b6dcaa55c3c08 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Tue, 26 Sep 2023 14:36:19 +0300 Subject: [PATCH 14/56] [DSC-1248] add new input 'configuration' to searchFormComponent --- .../shared/search-form/search-form.component.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 7ea51e4c1e0..1d29ea8ab37 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -42,8 +42,12 @@ export class SearchFormComponent implements OnInit { /** * The currently selected scope object's UUID */ - @Input() - scope = ''; + @Input() scope = ''; + + /** + * Discovery configuration to be used in search + */ + @Input() configuration: string; selectedScope: BehaviorSubject = new BehaviorSubject(undefined); @@ -99,8 +103,13 @@ export class SearchFormComponent implements OnInit { */ onSubmit(data: any) { if (isNotEmpty(this.scope)) { - data = Object.assign(data, { scope: this.scope }); + data = { ...data, scope: this.scope }; } + + if (isNotEmpty(this.configuration)) { + data = { ...data, configuration: this.configuration }; + } + this.updateSearch(data); this.submitSearch.emit(data); } From 1e4dcf25a8289980760ba8f81e597fa5ef00b4c9 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Tue, 26 Sep 2023 14:37:09 +0300 Subject: [PATCH 15/56] [DSC-1248] set new input 'configuration' to 'site' in search-section.component.html --- .../search-section/search-section.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/explore/section-component/search-section/search-section.component.html b/src/app/shared/explore/section-component/search-section/search-section.component.html index 187454424f2..57cf6e66447 100644 --- a/src/app/shared/explore/section-component/search-section/search-section.component.html +++ b/src/app/shared/explore/section-component/search-section/search-section.component.html @@ -27,7 +27,9 @@

{{ 'explore.search-section.' + sectionId
- >
From ff59759c1d0ecbcb2af0d4ca28f4ddb9e8b31ffb Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Tue, 26 Sep 2023 14:38:53 +0300 Subject: [PATCH 16/56] [DSC-1248] update i18n labels --- src/assets/i18n/en.json5 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 5f5b1797b9a..415f397a872 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5094,6 +5094,8 @@ "search.filters.applied.charts.default.title": "Search Output", + "search.filters.applied.charts.site.title": "Search Output", + "search.filters.applied.charts.RELATION.Person.researchoutputs.title": "Research Output", "search.filters.applied.charts.RELATION.Project.fundings.title": "Fundings", @@ -5353,6 +5355,8 @@ "default.search.results.head": "Search Results", + "site.search.results.head": "Search Results", + "default-relationships.search.results.head": "Search Results", "defaultConfiguration.search.results.head": "Search Results", From 2dba1c65cb8a5a03dde54b8c9b852c55414be30e Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 26 Sep 2023 17:40:59 +0200 Subject: [PATCH 17/56] [CST-10703] new wireframe implementation & validate email page --- src/app/app-routing.module.ts | 10 +++ ...-email-confirmation-page-routing.module.ts | 17 ++++ ...gin-email-confirmation-page.component.html | 3 + ...in-email-confirmation-page.component.scss} | 0 ...-email-confirmation-page.component.spec.ts | 25 ++++++ ...login-email-confirmation-page.component.ts | 8 ++ ...al-login-email-confirmation-page.module.ts | 19 ++++ .../external-login-page-routing.module.ts | 9 +- .../external-login-page.component.html | 1 + .../external-login-page.component.spec.ts | 9 +- .../email-validated.component.html | 2 +- .../email-validated.component.scss | 0 .../email-validated.component.spec.ts | 38 +++----- .../email-validated.component.ts | 21 +++++ ...al-login-validation-page-routing.module.ts | 17 ++++ ...ernal-login-validation-page.component.html | 8 ++ ...ernal-login-validation-page.component.scss | 0 ...al-login-validation-page.component.spec.ts | 25 ++++++ ...xternal-login-validation-page.component.ts | 21 +++++ .../external-login-validation-page.module.ts | 30 +++++++ .../helpers/compare-values.pipe.ts | 15 ++++ .../review-account-info.component.html | 45 ++++++++++ .../review-account-info.component.scss | 13 +++ .../review-account-info.component.spec.ts | 25 ++++++ .../review-account-info.component.ts | 87 +++++++++++++++++++ ...xternal-login-validation-page.component.ts | 25 ++++++ .../confirm-email.component.html | 2 +- .../confirm-email.component.spec.ts | 22 ++++- .../confirmation-sent.component.html | 2 +- .../confirmation-sent.component.spec.ts | 18 ++-- .../email-validated.component.ts | 11 --- .../provide-email.component.html | 2 +- .../provide-email.component.spec.ts | 2 + .../external-log-in.component.html | 40 ++++++--- .../external-log-in.component.spec.ts | 16 ++-- .../external-log-in.component.ts | 55 ++++++++++-- .../models/registration-data.mock.model.ts | 6 +- .../orcid-confirmation.component.spec.ts | 1 + src/app/shared/log-in/log-in.component.html | 4 +- src/app/shared/log-in/log-in.component.ts | 5 +- src/app/shared/shared.module.ts | 8 +- 41 files changed, 578 insertions(+), 89 deletions(-) create mode 100644 src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts create mode 100644 src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.html rename src/app/{shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss => external-login-email-confirmation-page/external-login-email-confirmation-page.component.scss} (100%) create mode 100644 src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts create mode 100644 src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.ts create mode 100644 src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts rename src/app/{shared/external-log-in-complete/email-confirmation => external-login-validation-page}/email-validated/email-validated.component.html (73%) create mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.scss rename src/app/{shared/external-log-in-complete/email-confirmation => external-login-validation-page}/email-validated/email-validated.component.spec.ts (61%) create mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.ts create mode 100644 src/app/external-login-validation-page/external-login-validation-page-routing.module.ts create mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.html create mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.scss create mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.spec.ts create mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.ts create mode 100644 src/app/external-login-validation-page/external-login-validation-page.module.ts create mode 100644 src/app/external-login-validation-page/helpers/compare-values.pipe.ts create mode 100644 src/app/external-login-validation-page/review-account-info/review-account-info.component.html create mode 100644 src/app/external-login-validation-page/review-account-info/review-account-info.component.scss create mode 100644 src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts create mode 100644 src/app/external-login-validation-page/review-account-info/review-account-info.component.ts create mode 100644 src/app/external-login-validation-page/themed-external-login-validation-page.component.ts delete mode 100644 src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 82af5149d06..d5f3a5aecdc 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -176,6 +176,16 @@ import { RedirectService } from './redirect/redirect.service'; loadChildren: () => import('./external-login-page/external-login-page.module') .then((m) => m.ExternalLoginPageModule) }, + { + path: 'validate-email', + loadChildren: () => import('./external-login-validation-page/external-login-validation-page.module') + .then((m) => m.ExternalLoginValidationPageModule) + }, + { + path: 'email-confirmation', + loadChildren: () => import('./external-login-email-confirmation-page/external-login-email-confirmation-page.module') + .then((m) => m.ExternalLoginEmailConfirmationPageModule) + }, { path: 'logout', loadChildren: () => import('./logout-page/logout-page.module') diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts new file mode 100644 index 00000000000..e242d9ea1a9 --- /dev/null +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + component: ExternalLoginEmailConfirmationPageComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ExternalLoginEmailConfirmationPageRoutingModule { } diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.html b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.html new file mode 100644 index 00000000000..c1cf46032b4 --- /dev/null +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.scss similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.scss rename to src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.scss diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts new file mode 100644 index 00000000000..cd646f25cf0 --- /dev/null +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; + +describe('ExternalLoginEmailConfirmationPageComponent', () => { + let component: ExternalLoginEmailConfirmationPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExternalLoginEmailConfirmationPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalLoginEmailConfirmationPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.ts new file mode 100644 index 00000000000..615de74ea34 --- /dev/null +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './external-login-email-confirmation-page.component.html', + styleUrls: ['./external-login-email-confirmation-page.component.scss'] +}) +export class ExternalLoginEmailConfirmationPageComponent { +} diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts new file mode 100644 index 00000000000..36684f2f367 --- /dev/null +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ExternalLoginEmailConfirmationPageRoutingModule } from './external-login-email-confirmation-page-routing.module'; +import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; +import { SharedModule } from '../shared/shared.module'; + + +@NgModule({ + declarations: [ + ExternalLoginEmailConfirmationPageComponent + ], + imports: [ + CommonModule, + ExternalLoginEmailConfirmationPageRoutingModule, + SharedModule + ] +}) +export class ExternalLoginEmailConfirmationPageModule { } diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index 4e64d267de8..7adbbb7b034 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -1,7 +1,5 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; const routes: Routes = [ @@ -9,17 +7,12 @@ const routes: Routes = [ path: '', pathMatch: 'full', component: ThemedExternalLoginPageComponent, - // resolve: { breadcrumb: I18nBreadcrumbResolver }, - // data: { breadcrumbKey: 'external-login', title: 'login.title' }, }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], - providers: [ - I18nBreadcrumbResolver, - I18nBreadcrumbsService - ] + providers: [] }) export class ExternalLoginPageRoutingModule { } diff --git a/src/app/external-login-page/external-login-page.component.html b/src/app/external-login-page/external-login-page.component.html index d2daa24a561..7aa4ad6e837 100644 --- a/src/app/external-login-page/external-login-page.component.html +++ b/src/app/external-login-page/external-login-page.component.html @@ -1,3 +1,4 @@
+ diff --git a/src/app/external-login-page/external-login-page.component.spec.ts b/src/app/external-login-page/external-login-page.component.spec.ts index 56f0d81d189..20f46ae833c 100644 --- a/src/app/external-login-page/external-login-page.component.spec.ts +++ b/src/app/external-login-page/external-login-page.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginPageComponent } from './external-login-page.component'; +import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { Router } from '@angular/router'; +import { RouterMock } from '../shared/mocks/router.mock'; describe('ExternalLoginPageComponent', () => { let component: ExternalLoginPageComponent; @@ -8,7 +11,11 @@ describe('ExternalLoginPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ExternalLoginPageComponent ] + declarations: [ ExternalLoginPageComponent ], + providers: [ + { provide: EpersonRegistrationService, useValue: {} }, + { provide: Router, useValue: new RouterMock() }, + ], }) .compileComponents(); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html b/src/app/external-login-validation-page/email-validated/email-validated.component.html similarity index 73% rename from src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html rename to src/app/external-login-validation-page/email-validated/email-validated.component.html index 3fa31b0bd53..133243853bf 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.html +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.html @@ -5,5 +5,5 @@

- +
diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.scss b/src/app/external-login-validation-page/email-validated/email-validated.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts similarity index 61% rename from src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts rename to src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts index e961e6fb913..d5d2c2794cc 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.spec.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts @@ -2,22 +2,29 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmailValidatedComponent } from './email-validated.component'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { BehaviorSubject } from 'rxjs'; -import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; +import { of } from 'rxjs'; +import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock'; describe('EmailValidatedComponent', () => { let component: EmailValidatedComponent; let fixture: ComponentFixture; let compiledTemplate: HTMLElement; + const translateServiceStub = { + get: () => of('Mocked Translation Text'), + instant: (key: any) => 'Mocked Translation Text', + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ EmailValidatedComponent ], providers: [ - { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: TranslateService, useValue: translateServiceStub }, ], imports: [ CommonModule, @@ -46,34 +53,17 @@ describe('EmailValidatedComponent', () => { it('should render translated header', () => { const headerElement = compiledTemplate.querySelector('h4'); - expect(headerElement.textContent).toContain('Mocked Header Translation'); + expect(headerElement.textContent).toContain('Mocked Translation Text'); }); it('should render translated info paragraph', () => { const infoParagraphElement = compiledTemplate.querySelector('p'); - expect(infoParagraphElement.innerHTML).toContain('Mocked Info Translation'); + expect(infoParagraphElement.innerHTML).toBeTruthy(); }); it('should render ds-log-in component', () => { const dsLogInComponent = compiledTemplate.querySelector('ds-log-in'); expect(dsLogInComponent).toBeTruthy(); }); - }); -// Mock the TranslateService -class MockTranslateService { - private translationSubject = new BehaviorSubject({}); - - get(key: string) { - const translations = { - 'external-login.validated-email.header': 'Mocked Header Translation', - 'external-login.validated-email.info': 'Mocked Info Translation', - }; - - this.translationSubject.next(translations); - - // Return an Observable that mimics TranslateService's behavior - return this.translationSubject.asObservable(); - } -} diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts new file mode 100644 index 00000000000..0703dbed499 --- /dev/null +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.ts @@ -0,0 +1,21 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from '../../core/auth/auth.service'; + +@Component({ + selector: 'ds-email-validated', + templateUrl: './email-validated.component.html', + styleUrls: ['./email-validated.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class EmailValidatedComponent { + constructor(private authService: AuthService, private router: Router) { + // if user is logged in, redirect to home page + // in case user logs in with an existing account + this.authService.isAuthenticated().subscribe((isAuthenticated: boolean) => { + if (isAuthenticated) { + this.router.navigate(['/']); + } + }); + } +} diff --git a/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts b/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts new file mode 100644 index 00000000000..d206eba0fe8 --- /dev/null +++ b/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + component: ThemedExternalLoginValidationPageComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ExternalLoginValidationPageRoutingModule { } diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html new file mode 100644 index 00000000000..10e7e99c261 --- /dev/null +++ b/src/app/external-login-validation-page/external-login-validation-page.component.html @@ -0,0 +1,8 @@ +
+ + + + + + +
diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.scss b/src/app/external-login-validation-page/external-login-validation-page.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts new file mode 100644 index 00000000000..3ec5c77409f --- /dev/null +++ b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; + +describe('ExternalLoginValidationPageComponent', () => { + let component: ExternalLoginValidationPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExternalLoginValidationPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalLoginValidationPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts new file mode 100644 index 00000000000..4a2ab4ef942 --- /dev/null +++ b/src/app/external-login-validation-page/external-login-validation-page.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + templateUrl: './external-login-validation-page.component.html', + styleUrls: ['./external-login-validation-page.component.scss'] +}) +export class ExternalLoginValidationPageComponent implements OnInit { + + // temporary variable to test the component + newEmail = false; + + existingEmail = true; + + ngOnInit(): void { + // GET data from validation link + // -> if email address is not used by other user => Email Validated component + // -> if email address is used by other user => Review account information component + console.log('ExternalLoginValidationPageComponent ngOnInit'); + } + +} diff --git a/src/app/external-login-validation-page/external-login-validation-page.module.ts b/src/app/external-login-validation-page/external-login-validation-page.module.ts new file mode 100644 index 00000000000..ba41cf95a1b --- /dev/null +++ b/src/app/external-login-validation-page/external-login-validation-page.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ExternalLoginValidationPageRoutingModule } from './external-login-validation-page-routing.module'; +import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; +import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; + +import { UiSwitchModule } from 'ngx-ui-switch'; +import { ReviewAccountInfoComponent } from './review-account-info/review-account-info.component'; +import { EmailValidatedComponent } from './email-validated/email-validated.component'; +import { SharedModule } from '../shared/shared.module'; +import { CompareValuesPipe } from './helpers/compare-values.pipe'; + + +@NgModule({ + declarations: [ + ExternalLoginValidationPageComponent, + ThemedExternalLoginValidationPageComponent, + ReviewAccountInfoComponent, + EmailValidatedComponent, + CompareValuesPipe + ], + imports: [ + CommonModule, + ExternalLoginValidationPageRoutingModule, + SharedModule, + UiSwitchModule, + ] +}) +export class ExternalLoginValidationPageModule { } diff --git a/src/app/external-login-validation-page/helpers/compare-values.pipe.ts b/src/app/external-login-validation-page/helpers/compare-values.pipe.ts new file mode 100644 index 00000000000..aefbe9a0ae0 --- /dev/null +++ b/src/app/external-login-validation-page/helpers/compare-values.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'dsCompareValues' +}) +export class CompareValuesPipe implements PipeTransform { + + transform(receivedValue: string, currentValue: string): unknown { + if (receivedValue === currentValue) { + return ''; + } else { + return currentValue; + } + } +} diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html new file mode 100644 index 00000000000..23e6282f52c --- /dev/null +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html @@ -0,0 +1,45 @@ +

Review your account information

+ +

+ The information received from ORCID differs from the one recorded in your + profile.
+ Please review them and decide if you want to update any of them.After saving + you will be redirected to your profile page. +

+
+ + + + + + + + + + + + + + + + + +
+ Information + + Received value + + Current value + Override
{{ data.label }}{{ data.receivedValue }} + + + + +
+
diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.scss b/src/app/external-login-validation-page/review-account-info/review-account-info.component.scss new file mode 100644 index 00000000000..1e531f0d8b9 --- /dev/null +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.scss @@ -0,0 +1,13 @@ +:host { + table { + tbody { + background-color: #f7f8f9; + } + + td, + th { + height: 60px; + vertical-align: middle; + } + } +} diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts new file mode 100644 index 00000000000..7cd24cbeec2 --- /dev/null +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ReviewAccountInfoComponent } from './review-account-info.component'; + +describe('ReviewAccountInfoComponent', () => { + let component: ReviewAccountInfoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ReviewAccountInfoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ReviewAccountInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts new file mode 100644 index 00000000000..92e8a07239f --- /dev/null +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts @@ -0,0 +1,87 @@ +import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { EPersonMock } from '../../shared/testing/eperson.mock'; +import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; +import { mockRegistrationDataModel } from '../../shared/external-log-in-complete/models/registration-data.mock.model'; +export interface ReviewAccountInfoData { + label: string; + currentValue: string; + receivedValue: string; + overrideValue: boolean; + identifier: string; +} + +@Component({ + selector: 'ds-review-account-info', + templateUrl: './review-account-info.component.html', + styleUrls: ['./review-account-info.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReviewAccountInfoComponent implements OnInit { + registeredData: RegistrationData = mockRegistrationDataModel; + + epersonData: EPerson = EPersonMock; + + notApplicable = 'N/A'; + + dataToCompare: ReviewAccountInfoData[] = []; + + constructor(private ePersonService: EPersonDataService) { + // GET data from url validation link and display + } + + ngOnInit(): void { + this.dataToCompare = [ + { + label: this.registeredData.registrationType, + receivedValue: this.registeredData.netId, + currentValue: this.notApplicable, + overrideValue: false, + identifier: 'netId', + }, + { + label: 'Last Name', + receivedValue: this.getReceivedValue('eperson.lastname'), + currentValue: this.getCurrentValue('eperson.lastname'), + overrideValue: false, + identifier: 'eperson.lastname', + }, + { + label: 'First Name', + currentValue: this.getCurrentValue('eperson.firstname'), + receivedValue: this.getReceivedValue('eperson.firstname'), + overrideValue: false, + identifier: 'eperson.firstname', + }, + { + label: 'Email', + currentValue: this.epersonData.email, + receivedValue: this.registeredData.email, + overrideValue: false, + identifier: 'email', + }, + ]; + } + + getEPersonData() { + // this.epersonData$ = this.ePersonService.findById() + // .pipe( + // getFirstCompletedRemoteData(), + // getRemoteDataPayload() + // ); + } + + getReceivedValue(metadata: string): string { + return this.registeredData.registrationMetadata[metadata]?.[0]?.value; + } + + getCurrentValue(metadata: string): string { + return this.epersonData.firstMetadataValue(metadata); + } + + test(value: boolean, identifier: string) { + this.dataToCompare.find((data) => data.identifier === identifier).overrideValue = value; + console.log(this.dataToCompare); + } +} diff --git a/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts b/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts new file mode 100644 index 00000000000..a08b2669deb --- /dev/null +++ b/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../shared/theme-support/themed.component'; +import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; + +/** + * Themed wrapper for ExternalLoginValidationPageComponent + */ +@Component({ + selector: 'ds-themed-external-login-page', + styleUrls: [], + templateUrl: './../shared/theme-support/themed.component.html' +}) +export class ThemedExternalLoginValidationPageComponent extends ThemedComponent { + protected getComponentName(): string { + return 'ExternalLoginValidationPageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../themes/${themeName}/app/external-login-validation-page/external-login-validation-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./external-login-validation-page.component`); + } +} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html index 97d9869f411..2ff2000d41a 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html @@ -31,7 +31,7 @@

- diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index 8e426f27fc0..58b2b2414cd 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -1,6 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmEmailComponent } from './confirm-email.component'; +import { FormBuilder } from '@angular/forms'; +import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; +import { CommonModule } from '@angular/common'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; @@ -8,7 +14,21 @@ describe('ConfirmEmailComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ConfirmEmailComponent ] + declarations: [ ConfirmEmailComponent ], + providers: [ + FormBuilder, + { provide: EpersonRegistrationService, useValue: {} }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) .compileComponents(); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html index f81ebf1a170..3d79b16a4e2 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html @@ -2,4 +2,4 @@

{{ "external-login.confirm-email-sent.header" | translate }}

-

+

diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts index 5106220db62..3de88d9ba26 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts @@ -2,20 +2,29 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmationSentComponent } from './confirmation-sent.component'; import { CommonModule } from '@angular/common'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { TranslateService, TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { of } from 'rxjs'; describe('ConfirmationSentComponent', () => { let component: ConfirmationSentComponent; let fixture: ComponentFixture; let compiledTemplate: HTMLElement; + const translateServiceStub = { + get: () => of('Mocked Translation Text'), + instant: (key: any) => 'Mocked Translation Text', + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ ConfirmationSentComponent ], providers: [ - { provide: TranslateService, useClass: {} }, + { provide: TranslateService, useValue: translateServiceStub }, ], imports: [ CommonModule, @@ -44,12 +53,11 @@ describe('ConfirmationSentComponent', () => { it('should render translated header', () => { const headerElement = compiledTemplate.querySelector('h4'); - expect(headerElement.textContent).toContain('Mocked Header Translation'); + expect(headerElement.textContent).toContain('Mocked Translation Text'); }); it('should render translated info paragraph', () => { const infoParagraphElement = compiledTemplate.querySelector('p'); - expect(infoParagraphElement.innerHTML).toContain('Mocked Info Translation'); + expect(infoParagraphElement.innerHTML).toBeTruthy(); }); - }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts deleted file mode 100644 index c74cbcfd489..00000000000 --- a/src/app/shared/external-log-in-complete/email-confirmation/email-validated/email-validated.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'ds-email-validated', - templateUrl: './email-validated.component.html', - styleUrls: ['./email-validated.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class EmailValidatedComponent { - -} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html index 72c8eb7c88c..b5a2efa7b68 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html @@ -32,7 +32,7 @@

- diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts index 03e28c687d3..63e64830394 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts @@ -6,6 +6,7 @@ import { CommonModule } from '@angular/common'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; describe('ProvideEmailComponent', () => { let component: ProvideEmailComponent; @@ -16,6 +17,7 @@ describe('ProvideEmailComponent', () => { declarations: [ ProvideEmailComponent ], providers: [ FormBuilder, + { provide: EpersonRegistrationService, useValue: {} }, ], imports: [ CommonModule, diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html index e9b5f27f572..59219922470 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -8,22 +8,42 @@

{{ 'external-login.confirmation.header' | translate}}

{{ informationText }}
-
-
- +
+
+
-
-

or

+
+

or

-
- +
+
+ + + + + + diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts index 9268ebeb817..d29ebdb8513 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts @@ -15,8 +15,8 @@ describe('ExternalLogInComponent', () => { let fixture: ComponentFixture; let compiledTemplate: HTMLElement; const translateServiceStub = { - get: () => observableOf('Mocked Translation Text'), - instant: (key: any) => 'Mocked Translation Text', + get: () => observableOf('Info Text'), + instant: (key: any) => 'Info Text', onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), onDefaultLangChange: new EventEmitter() @@ -48,6 +48,7 @@ describe('ExternalLogInComponent', () => { fixture = TestBed.createComponent(ExternalLogInComponent); component = fixture.componentInstance; component.registrationData = mockRegistrationDataModel; + component.registrationType = mockRegistrationDataModel.registrationType; compiledTemplate = fixture.nativeElement; fixture.detectChanges(); }); @@ -58,17 +59,20 @@ describe('ExternalLogInComponent', () => { it('should set registrationType and informationText correctly when email is present', () => { expect(component.registrationType).toBe('orcid'); - expect(component.informationText).toContain('orcid'); + expect(component.informationText).toBeDefined(); }); it('should render the template to confirm email when registrationData has email', () => { + component.registrationData = Object.assign({}, mockRegistrationDataModel, { email: 'test@user.com' }); + fixture.detectChanges(); const selector = compiledTemplate.querySelector('ds-confirm-email'); expect(selector).toBeTruthy(); }); it('should render the template with provide email component when registrationData email is null', () => { - component.registrationData.email = null; + component.registrationData = Object.assign({}, mockRegistrationDataModel, { email: null }); fixture.detectChanges(); + component.ngOnInit(); const provideEmailComponent = compiledTemplate.querySelector('ds-provide-email'); expect(provideEmailComponent).toBeTruthy(); }); @@ -79,10 +83,10 @@ describe('ExternalLogInComponent', () => { }); it('should render the template with the translated informationText', () => { - component.informationText = 'Mocked Translation Text'; + component.informationText = 'Info Text'; fixture.detectChanges(); const infoText = fixture.debugElement.query(By.css('[data-test="info-text"]')); - expect(infoText.nativeElement.innerHTML).toContain('Mocked Translation Text'); + expect(infoText.nativeElement.innerHTML).toContain('Info Text'); }); }); diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts index 091f3750398..3b82ab70ca1 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -1,21 +1,30 @@ -import { Component, OnInit, ChangeDetectionStrategy, Input, Injector } from '@angular/core'; +import { + Component, + OnInit, + ChangeDetectionStrategy, + Input, + Injector, +} from '@angular/core'; import { getExternalLoginConfirmationType } from '../external-log-in.methods-decorator'; import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; import { RegistrationData } from '../models/registration-data.model'; import { hasValue } from '../../empty.util'; import { TranslateService } from '@ngx-translate/core'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { AuthService } from '../../../core/auth/auth.service'; +import { Router } from '@angular/router'; @Component({ selector: 'ds-external-log-in', templateUrl: './external-log-in.component.html', styleUrls: ['./external-log-in.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExternalLogInComponent implements OnInit { /** * The type of registration type to be confirmed */ - registrationType: AuthMethodType = AuthMethodType.Orcid; + registrationType: AuthMethodType; /** * The registration data object */ @@ -37,10 +46,25 @@ export class ExternalLogInComponent implements OnInit { */ public objectInjector: Injector; + /** + * Reference to NgbModal + */ + public modalRef: NgbModalRef; + constructor( private injector: Injector, private translate: TranslateService, + private modalService: NgbModal, + private authService: AuthService, + private router: Router ) { + // if user is logged in, redirect to home page + // in case user logs in with an existing account + this.authService.isAuthenticated().subscribe((isAuthenticated: boolean) => { + if (isAuthenticated) { + this.router.navigate(['/']); + } + }); } /** @@ -50,9 +74,13 @@ export class ExternalLogInComponent implements OnInit { ngOnInit(): void { this.objectInjector = Injector.create({ providers: [ - { provide: 'registrationDataProvider', useFactory: () => (this.registrationData), deps: [] }, + { + provide: 'registrationDataProvider', + useFactory: () => this.registrationData, + deps: [], + }, ], - parent: this.injector + parent: this.injector, }); this.registrationType = this.registrationData?.registrationType ?? null; this.informationText = hasValue(this.registrationData?.email) @@ -67,7 +95,9 @@ export class ExternalLogInComponent implements OnInit { private generateInformationTextWhenNOEmail(authMethod: string): string { if (authMethod) { const authMethodUppercase = authMethod.toUpperCase(); - return this.translate.instant('external-login.noEmail.informationText', { authMethod: authMethodUppercase }); + return this.translate.instant('external-login.noEmail.informationText', { + authMethod: authMethodUppercase, + }); } } @@ -78,7 +108,10 @@ export class ExternalLogInComponent implements OnInit { private generateInformationTextWhenEmail(authMethod: string): string { if (authMethod) { const authMethodUppercase = authMethod.toUpperCase(); - return this.translate.instant('external-login.haveEmail.informationText', { authMethod: authMethodUppercase }); + return this.translate.instant( + 'external-login.haveEmail.informationText', + { authMethod: authMethodUppercase } + ); } } @@ -88,4 +121,12 @@ export class ExternalLogInComponent implements OnInit { getExternalLoginConfirmationType() { return getExternalLoginConfirmationType(this.registrationType); } + + openLoginModal(content: any) { + this.modalRef = this.modalService.open(content); + } + + ngOnDestroy(): void { + this.modalRef?.close(); + } } diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts index 3fdf739e27e..30b7b8526bc 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -1,6 +1,6 @@ import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; import { RegistrationData } from './registration-data.model'; -import { MetadataValue } from 'src/app/core/shared/metadata.models'; +import { MetadataValue } from '../../../core/shared/metadata.models'; export const mockRegistrationDataModel: RegistrationData = Object.assign( new RegistrationData(), { id: '3', @@ -11,7 +11,7 @@ export const mockRegistrationDataModel: RegistrationData = Object.assign( new Re registrationMetadata: { 'eperson.firstname': [ Object.assign(new MetadataValue(), { - value: 'Power', + value: 'User', language: null, authority: '', confidence: -1, @@ -20,7 +20,7 @@ export const mockRegistrationDataModel: RegistrationData = Object.assign( new Re ], 'eperson.lastname': [ Object.assign(new MetadataValue(), { - value: 'User', + value: 'Power', language: null, authority: '', confidence: -1, diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts index 3c856e6f283..e3f84663539 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts @@ -68,6 +68,7 @@ describe('OrcidConfirmationComponent', () => { it('should not render email input when email is null', () => { component.registratioData.email = null; fixture.detectChanges(); + component.ngOnInit(); const emailInput = fixture.nativeElement.querySelector('input[type="email"]'); expect(emailInput).toBeFalsy(); }); diff --git a/src/app/shared/log-in/log-in.component.html b/src/app/shared/log-in/log-in.component.html index 11d306c1bc4..7adea60aa1d 100644 --- a/src/app/shared/log-in/log-in.component.html +++ b/src/app/shared/log-in/log-in.component.html @@ -7,9 +7,9 @@ - + - {{"login.form.new-user" | translate}} + {{"login.form.new-user" | translate}} {{"login.form.forgot-password" | translate}}
diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 1fe595c1ed2..18a7d6ddf98 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { Observable, Subscription, take } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { select, Store } from '@ngrx/store'; import uniqBy from 'lodash/uniqBy'; @@ -18,7 +18,6 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { CoreState } from '../../core/core-state.model'; import { AuthMethodType } from '../../core/auth/models/auth.method-type'; -import de from 'date-fns/esm/locale/de/index.js'; /** * /users/sign-in @@ -41,6 +40,8 @@ export class LogInComponent implements OnInit, OnDestroy { @Input() showRegisterLink = true; + @Input() hideAllLinks = false; + /** * The list of authentication methods available * @type {AuthMethod[]} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 3ad876a114e..65bcd2dfa3c 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -337,7 +337,6 @@ import { OrcidConfirmationComponent } from './external-log-in-complete/registrat import { ProvideEmailComponent } from './external-log-in-complete/email-confirmation/provide-email/provide-email.component'; import { ConfirmEmailComponent } from './external-log-in-complete/email-confirmation/confirm-email/confirm-email.component'; import { ConfirmationSentComponent } from './external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; -import { EmailValidatedComponent } from './external-log-in-complete/email-confirmation/email-validated/email-validated.component'; const MODULES = [ CommonModule, @@ -477,6 +476,9 @@ const COMPONENTS = [ ThemedBrowseMostElementsComponent, SearchChartBarHorizontalComponent, ExternalLogInComponent, + ProvideEmailComponent, + ConfirmEmailComponent, + ConfirmationSentComponent, ]; const ENTRY_COMPONENTS = [ @@ -591,10 +593,6 @@ const DIRECTIVES = [ ...COMPONENTS, ...ENTRY_COMPONENTS, ...DIRECTIVES, - ProvideEmailComponent, - ConfirmEmailComponent, - ConfirmationSentComponent, - EmailValidatedComponent, ], providers: [ ...PROVIDERS From d413423c4582507dff6aa68d7aaf749805a13855 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 27 Sep 2023 17:41:08 +0200 Subject: [PATCH 18/56] [CST-10703] (partial commit) merge account information & validate email --- .../core/data/eperson-registration.service.ts | 18 +-- src/app/core/eperson/eperson-data.service.ts | 21 +++ .../external-login-page.component.ts | 9 +- .../email-validated.component.ts | 6 +- ...ernal-login-validation-page.component.html | 11 +- ...xternal-login-validation-page.component.ts | 51 ++++++- .../review-account-info.component.html | 44 ++++-- .../review-account-info.component.ts | 131 ++++++++++++------ .../confirm-email/confirm-email.component.ts | 17 ++- .../provide-email/provide-email.component.ts | 18 +-- .../models/registration-data.mock.model.ts | 2 +- .../services/external-login.service.spec.ts | 16 +++ .../services/external-login.service.ts | 37 +++++ src/assets/i18n/en.json5 | 19 +++ src/assets/i18n/it.json5 | 89 ++++++++++++ 15 files changed, 378 insertions(+), 111 deletions(-) create mode 100644 src/app/shared/external-log-in-complete/services/external-login.service.spec.ts create mode 100644 src/app/shared/external-log-in-complete/services/external-login.service.ts diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index e1b77977d6a..3385c520dc5 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -150,7 +150,7 @@ export class EpersonRegistrationService{ * @param updateValue Flag to indicate if the email should be updated or added * @returns Remote Data state of the patch request */ - patchUpdateRegistration(value: string, field: string, registrationId: string, token: string, updateValue: boolean) { + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operator: 'add' | 'replace') { const requestId = this.requestService.generateRequestId(); const href$ = this.getRegistrationEndpoint().pipe( @@ -159,7 +159,7 @@ export class EpersonRegistrationService{ ); href$.subscribe((href: string) => { - const operations = this.generateOperations(value, field, updateValue); + const operations = this.generateOperations(values, field, operator); const patchRequest = new PatchRequest(requestId, href, operations); this.requestService.send(patchRequest); }); @@ -173,17 +173,11 @@ export class EpersonRegistrationService{ * @param updateValue Flag to indicate if the email should be updated or added * @returns Operations to be performed on the registration object */ - private generateOperations(value: string, field: string, updateValue: boolean): Operation[] { + private generateOperations(values: string[], field: string, operator: 'add' | 'replace'): Operation[] { let operations = []; - if (hasValue(value) && updateValue) { - operations = [...operations, { - op: 'replace', path: `/${field}`, value: value - }]; - } - - if (hasValue(value) && !updateValue) { - operations = [...operations, { - op: 'add', path: `/${field}`, value: value + if (values.length > 0 && hasValue(field) ) { + operations = [{ + op: operator, path: `/${field}`, value: values }]; } diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 87827bcc592..affbe6d045d 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -354,6 +354,27 @@ export class EPersonDataService extends IdentifiableDataService impleme return this.rdbService.buildFromRequestUUID(requestId); } + /** + * Sends a POST request to merge registration data related to the provided registration-token, + * into the eperson related to the provided uuid + * @param uuid the user uuid + * @param token registration-token + * @param metadataKey metadata key of the metadata field that should be overriden + */ + mergeEPersonDataWithToken(uuid: string, token: string, metadataKey: string): Observable> { + const requestId = this.requestService.generateRequestId(); + const hrefObs = this.getBrowseEndpoint().pipe( + map((href: string) => `${href}/${uuid}?token=${token}&override=${metadataKey}`)); + + hrefObs.pipe( + find((href: string) => hasValue(href)), + ).subscribe((href: string) => { + const request = new PostRequest(requestId, href); + this.requestService.send(request); + }); + + return this.rdbService.buildFromRequestUUID(requestId); + } /** * Create a new object on the server, and store the response in the object cache diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index 94ab4424b86..91bd59ed3c9 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { hasValue } from '../shared/empty.util'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { RemoteData } from '../core/data/remote-data'; @@ -19,15 +19,12 @@ export class ExternalLoginPageComponent implements OnInit { constructor( private epersonRegistrationService: EpersonRegistrationService, - private router: Router, + private arouter: ActivatedRoute, ) { - this.token = this.router.parseUrl(this.router.url).queryParams.token; + this.token = this.arouter.snapshot.queryParams.token; } ngOnInit(): void { - // TODO: call the method getTokenSearchEndpoint (eperson-registration.service.ts ) protected searchByTokenPath = '/search/findByToken?token='; - // token will be provided by the url (returned by REST API) - console.log('ExternalLoginPageComponent ngOnInit'); if (hasValue(this.token)) { this.epersonRegistrationService.searchByToken(this.token).subscribe((registration: RemoteData ) => { diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts index 0703dbed499..1ff85244386 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.ts @@ -1,7 +1,6 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from '../../core/auth/auth.service'; - @Component({ selector: 'ds-email-validated', templateUrl: './email-validated.component.html', @@ -9,6 +8,9 @@ import { AuthService } from '../../core/auth/auth.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class EmailValidatedComponent { + + @Input() registrationToken: string; + constructor(private authService: AuthService, private router: Router) { // if user is logged in, redirect to home page // in case user logs in with an existing account diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html index 10e7e99c261..227dc740988 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.html +++ b/src/app/external-login-validation-page/external-login-validation-page.component.html @@ -1,8 +1,11 @@
- - + + - - + +
diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts index 4a2ab4ef942..2f3be1c4e0c 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.ts @@ -1,21 +1,58 @@ import { Component, OnInit } from '@angular/core'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; +import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { ActivatedRoute } from '@angular/router'; +import { hasValue } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { Registration } from '../core/shared/registration.model'; +import { Observable, map, of, tap } from 'rxjs'; +import { getRemoteDataPayload } from '../core/shared/operators'; @Component({ templateUrl: './external-login-validation-page.component.html', - styleUrls: ['./external-login-validation-page.component.scss'] + styleUrls: ['./external-login-validation-page.component.scss'], }) export class ExternalLoginValidationPageComponent implements OnInit { + /** + * Whether or not the email address is already used by another user + */ + public emailExists: boolean; + /** + * The token used to get the registration data + */ + public token: string; - // temporary variable to test the component - newEmail = false; + /** + * The registration data of the user + */ + public registrationData$: Observable = of( + mockRegistrationDataModel + ); - existingEmail = true; + constructor( + private epersonRegistrationService: EpersonRegistrationService, + private arouter: ActivatedRoute + ) { + this.token = this.arouter.snapshot.queryParams.token; + this.emailExists = true; + } ngOnInit(): void { - // GET data from validation link // -> if email address is not used by other user => Email Validated component // -> if email address is used by other user => Review account information component - console.log('ExternalLoginValidationPageComponent ngOnInit'); + if (hasValue(this.token)) { + this.registrationData$ = this.epersonRegistrationService + .searchByToken(this.token) + .pipe( + tap((registration: RemoteData) => { + this.emailExists = hasValue(registration.payload.email); + }), + getRemoteDataPayload(), + map((registration: Registration) => + Object.assign(new RegistrationData(), registration) + ) + ); + } } - } diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html index 23e6282f52c..98a01d734ee 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html @@ -1,45 +1,59 @@ -

Review your account information

+

{{'external-login-validation.review-account-info.header' | translate}}

-

- The information received from ORCID differs from the one recorded in your - profile.
- Please review them and decide if you want to update any of them.After saving - you will be redirected to your profile page. -

+

- + + + + + + + - +
- Information + {{ 'external-login-validation.review-account-info.table.header.information' | translate }} - Received value + {{'external-login-validation.review-account-info.table.header.received-value' | translate }} - Current value + {{'external-login-validation.review-account-info.table.header.current-value' | translate }} Override{{'external-login-validation.review-account-info.table.header.action' | translate }}
{{ registrationData.registrationType | uppercase }}{{ registrationData.netId }} + + {{ notApplicableText }} + +
{{ data.label }}{{ data.label | titlecase }} {{ data.receivedValue }} - +
+
+ +
diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts index 92e8a07239f..3d244a6294d 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts @@ -1,9 +1,18 @@ -import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { + Component, + ChangeDetectionStrategy, + OnInit, + Input, +} from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { EPersonMock } from '../../shared/testing/eperson.mock'; import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; -import { mockRegistrationDataModel } from '../../shared/external-log-in-complete/models/registration-data.mock.model'; +import { filter, from, switchMap, take } from 'rxjs'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { ConfirmationModalComponent } from 'src/app/shared/confirmation-modal/confirmation-modal.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + export interface ReviewAccountInfoData { label: string; currentValue: string; @@ -19,49 +28,48 @@ export interface ReviewAccountInfoData { changeDetection: ChangeDetectionStrategy.OnPush, }) export class ReviewAccountInfoComponent implements OnInit { - registeredData: RegistrationData = mockRegistrationDataModel; + @Input() registrationToken: string; + + @Input() registrationData: RegistrationData; epersonData: EPerson = EPersonMock; - notApplicable = 'N/A'; + notApplicableText = 'N/A'; dataToCompare: ReviewAccountInfoData[] = []; - constructor(private ePersonService: EPersonDataService) { + constructor( + private ePersonService: EPersonDataService, + private modalService: NgbModal + ) { // GET data from url validation link and display + // Based on the URL data we get + // 1. token + // 1. login user + // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 + // the header in the review page must show that the user is logged in } ngOnInit(): void { - this.dataToCompare = [ - { - label: this.registeredData.registrationType, - receivedValue: this.registeredData.netId, - currentValue: this.notApplicable, - overrideValue: false, - identifier: 'netId', - }, - { - label: 'Last Name', - receivedValue: this.getReceivedValue('eperson.lastname'), - currentValue: this.getCurrentValue('eperson.lastname'), - overrideValue: false, - identifier: 'eperson.lastname', - }, - { - label: 'First Name', - currentValue: this.getCurrentValue('eperson.firstname'), - receivedValue: this.getReceivedValue('eperson.firstname'), - overrideValue: false, - identifier: 'eperson.firstname', - }, - { - label: 'Email', - currentValue: this.epersonData.email, - receivedValue: this.registeredData.email, - overrideValue: false, - identifier: 'email', - }, - ]; + this.dataToCompare = this.prepareDataToCompare(); + } + + private prepareDataToCompare() { + const dataToCompare: ReviewAccountInfoData[] = []; + Object.entries(this.registrationData.registrationMetadata).forEach( + ([key, value]) => { + console.log(key, value); + dataToCompare.push({ + label: key.split('.')?.[1], + currentValue: this.getCurrentValue(key), + receivedValue: value[0].value, + overrideValue: false, + identifier: key, + }); + } + ); + + return dataToCompare; } getEPersonData() { @@ -72,16 +80,55 @@ export class ReviewAccountInfoComponent implements OnInit { // ); } - getReceivedValue(metadata: string): string { - return this.registeredData.registrationMetadata[metadata]?.[0]?.value; + private getCurrentValue(metadata: string): string { + return this.epersonData.firstMetadataValue(metadata); } - getCurrentValue(metadata: string): string { - return this.epersonData.firstMetadataValue(metadata); + public onOverrideChange(value: boolean, identifier: string) { + this.dataToCompare.find( + (data) => data.identifier === identifier + ).overrideValue = value; } - test(value: boolean, identifier: string) { - this.dataToCompare.find((data) => data.identifier === identifier).overrideValue = value; - console.log(this.dataToCompare); + /** + * Merge the data from the registration token with the data from the eperson + */ + public mergeEPersonRegistrationData() { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.headerLabel = 'confirmation-modal.review-account-info.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.review-account-info.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.review-account-info.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.review-account-info.confirm'; + modalRef.componentInstance.brandColor = 'primary'; + modalRef.componentInstance.confirmIcon = 'fa fa-check'; + modalRef.componentInstance.response + .pipe(take(1)) + .subscribe((confirm: boolean) => { + if (confirm) { + from(this.dataToCompare) + .pipe( + // what happens when is not overriden? + filter((data: ReviewAccountInfoData) => data.overrideValue), + switchMap((data: ReviewAccountInfoData) => { + return this.ePersonService.mergeEPersonDataWithToken( + this.epersonData.id, + this.registrationToken, + data.identifier + ); + }) + ) + .subscribe((response: RemoteData) => { + // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 + // redirect to profile page + if (response.hasSucceeded) { + console.log(response.payload); + } + + if (response.hasFailed) { + console.log(response.errorMessage); + } + }); + } + }); } } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 0b177db851d..244521d2a65 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,7 +1,7 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; -import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; +import { ExternalLoginService } from '../../services/external-login.service'; +import { getRemoteDataPayload } from '../../../../core/shared/operators'; @Component({ selector: 'ds-confirm-email', @@ -19,7 +19,7 @@ export class ConfirmEmailComponent { constructor( private formBuilder: FormBuilder, - private epersonRegistrationService: EpersonRegistrationService, + private externalLoginService: ExternalLoginService, ) { this.emailForm = this.formBuilder.group({ email: ['', [Validators.required, Validators.email]] @@ -30,12 +30,11 @@ export class ConfirmEmailComponent { this.emailForm.markAllAsTouched(); if (this.emailForm.valid) { const email = this.emailForm.get('email').value; - console.log('Email submitted:', email); - this.epersonRegistrationService.patchUpdateRegistration(email, 'email', this.registrationId, this.token, true).pipe( - getFirstCompletedRemoteData() - ).subscribe((update) => { - console.log('Email update:', update); - }); + this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'replace') + .pipe(getRemoteDataPayload()) + .subscribe((update) => { + console.log('Email update:', update); + }); } } } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index 1d504c869f4..4b680656392 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -1,7 +1,7 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; -import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { getRemoteDataPayload } from '../../../../core/shared/operators'; +import { ExternalLoginService } from '../../services/external-login.service'; @Component({ selector: 'ds-provide-email', @@ -18,7 +18,7 @@ export class ProvideEmailComponent { constructor( private formBuilder: FormBuilder, - private epersonRegistrationService: EpersonRegistrationService + private externalLoginService: ExternalLoginService, ) { this.emailForm = this.formBuilder.group({ email: ['', [Validators.required, Validators.email]], @@ -29,16 +29,8 @@ export class ProvideEmailComponent { this.emailForm.markAllAsTouched(); if (this.emailForm.valid) { const email = this.emailForm.get('email').value; - console.log('Email submitted:', email); - this.epersonRegistrationService - .patchUpdateRegistration( - email, - 'email', - this.registrationId, - this.token, - true - ) - .pipe(getFirstCompletedRemoteData()) + this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'add') + .pipe(getRemoteDataPayload()) .subscribe((update) => { console.log('Email update:', update); }); diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts index 30b7b8526bc..8d02f349b05 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -7,7 +7,7 @@ export const mockRegistrationDataModel: RegistrationData = Object.assign( new Re email: 'user@institution.edu', user: '028dcbb8-0da2-4122-a0ea-254be49ca107', registrationType: AuthMethodType.Orcid, - netId: '<:orcid>', + netId: '0000-1111-2222-3333', registrationMetadata: { 'eperson.firstname': [ Object.assign(new MetadataValue(), { diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts new file mode 100644 index 00000000000..7d1d8ac6a2c --- /dev/null +++ b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ExternalLoginService } from './external-login.service'; + +describe('ExternalLoginService', () => { + let service: ExternalLoginService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ExternalLoginService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts new file mode 100644 index 00000000000..f0289eb5b78 --- /dev/null +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Observable, map } from 'rxjs'; +import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { NotificationsService } from '../../notifications/notifications.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ExternalLoginService { + + constructor( + private epersonRegistrationService: EpersonRegistrationService, + private router: Router, + private notificationService: NotificationsService + ) { } + + + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable> { + const updatedValues = values.map((value) => value); + return this.epersonRegistrationService.patchUpdateRegistration(updatedValues, field, registrationId, token, operation).pipe( + getFirstCompletedRemoteData(), + map((rd) => { + if (rd.hasSucceeded) { + this.router.navigate(['/email-confirmation']); + } + if (rd.hasFailed) { + console.log('Email update failed: email address was omitted or the operation is not valid', rd.errorMessage); + this.notificationService.error('Something went wrong.Email address was omitted or the operation is not valid'); + } + return rd; + }) + ); + } +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index faddb3a2b0e..328296818c4 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1998,6 +1998,13 @@ "confirmation-modal.pending-changes.confirm": "Leave", + "confirmation-modal.review-account-info.header": "Save the changes", + + "confirmation-modal.review-account-info.info": "Continue to update your profile", + + "confirmation-modal.review-account-info.cancel": "Cancel", + + "confirmation-modal.review-account-info.confirm": "Save", "dataset.listelement.badge" : "Dataset", @@ -7194,4 +7201,16 @@ "external-login.provide-email.header": "Provide email", "external-login.provide-email.button.label": "Send Verification link", + + "external-login-validation.review-account-info.header": "Review your account information", + + "external-login-validation.review-account-info.info": "The information received from ORCID differs from the one recorded in your profile.
Please review them and decide if you want to update any of them.After saving you will be redirected to your profile page.", + + "external-login-validation.review-account-info.table.header.information": "Information", + + "external-login-validation.review-account-info.table.header.received-value": "Received value", + + "external-login-validation.review-account-info.table.header.current-value": "Current value", + + "external-login-validation.review-account-info.table.header.action": "Override", } diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 81336daf11f..c4b8fdaff6e 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -3066,6 +3066,21 @@ // "confirmation-modal.pending-changes.confirm": "Leave", "confirmation-modal.pending-changes.confirm": "Abbandona", + // "confirmation-modal.review-account-info.header": "Save the changes", + // TODO New key - Add a translation + "confirmation-modal.review-account-info.header": "Save the changes", + + // "confirmation-modal.review-account-info.info": "Continue to update your profile", + // TODO New key - Add a translation + "confirmation-modal.review-account-info.info": "Continue to update your profile", + + // "confirmation-modal.review-account-info.cancel": "Cancel", + // TODO New key - Add a translation + "confirmation-modal.review-account-info.cancel": "Cancel", + + // "confirmation-modal.review-account-info.confirm": "Save", + // TODO New key - Add a translation + "confirmation-modal.review-account-info.confirm": "Save", // "dataset.listelement.badge" : "Dataset", "dataset.listelement.badge" : "Dataset", @@ -11217,5 +11232,79 @@ // TODO New key - Add a translation "admin.system-wide-alert.title": "System-wide Alerts", + // "external-login.confirmation.header": "Information needed to complete the login process", + // TODO New key - Add a translation + "external-login.confirmation.header": "Information needed to complete the login process", + + // "external-login.noEmail.informationText": "The information received from {{authMethod}} are not sufficient to complete the login process. Please provide the missing information below, or login via a different method to associate your {{authMethod}} to an existing account.", + // TODO New key - Add a translation + "external-login.noEmail.informationText": "The information received from {{authMethod}} are not sufficient to complete the login process. Please provide the missing information below, or login via a different method to associate your {{authMethod}} to an existing account.", + + // "external-login.haveEmail.informationText": "It seems that you have not yet an account in this system. If this is the case, please confirm the data received from {{authMethod}} and a new account will be created for you. Otherwise, if you already have an account in the system, please update the email address to match the one already in use in the system or login via a different method to associate your {{authMethod}} to your existing account.", + // TODO New key - Add a translation + "external-login.haveEmail.informationText": "It seems that you have not yet an account in this system. If this is the case, please confirm the data received from {{authMethod}} and a new account will be created for you. Otherwise, if you already have an account in the system, please update the email address to match the one already in use in the system or login via a different method to associate your {{authMethod}} to your existing account.", + + // "external-login.confirm-email.header": "Confirm or update email", + // TODO New key - Add a translation + "external-login.confirm-email.header": "Confirm or update email", + + // "external-login.confirmation.email-required": "Email is required.", + // TODO New key - Add a translation + "external-login.confirmation.email-required": "Email is required.", + + // "external-login.confirmation.email-invalid": "Invalid email format.", + // TODO New key - Add a translation + "external-login.confirmation.email-invalid": "Invalid email format.", + + // "external-login.confirm.button.label": "Confirm this email", + // TODO New key - Add a translation + "external-login.confirm.button.label": "Confirm this email", + + // "external-login.confirm-email-sent.header": "Confirmation email sent", + // TODO New key - Add a translation + "external-login.confirm-email-sent.header": "Confirmation email sent", + // "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", + // TODO New key - Add a translation + "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", + + // "external-login.validated-email.header": "Email validated", + // TODO New key - Add a translation + "external-login.validated-email.header": "Email validated", + + // "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", + // TODO New key - Add a translation + "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", + + // "external-login.provide-email.header": "Provide email", + // TODO New key - Add a translation + "external-login.provide-email.header": "Provide email", + + // "external-login.provide-email.button.label": "Send Verification link", + // TODO New key - Add a translation + "external-login.provide-email.button.label": "Send Verification link", + + // "external-login-validation.review-account-info.header": "Review your account information", + // TODO New key - Add a translation + "external-login-validation.review-account-info.header": "Review your account information", + + // "external-login-validation.review-account-info.info": "The information received from ORCID differs from the one recorded in your profile.
Please review them and decide if you want to update any of them.After saving you will be redirected to your profile page.", + // TODO New key - Add a translation + "external-login-validation.review-account-info.info": "The information received from ORCID differs from the one recorded in your profile.
Please review them and decide if you want to update any of them.After saving you will be redirected to your profile page.", + + // "external-login-validation.review-account-info.table.header.information": "Information", + // TODO New key - Add a translation + "external-login-validation.review-account-info.table.header.information": "Information", + + // "external-login-validation.review-account-info.table.header.received-value": "Received value", + // TODO New key - Add a translation + "external-login-validation.review-account-info.table.header.received-value": "Received value", + + // "external-login-validation.review-account-info.table.header.current-value": "Current value", + // TODO New key - Add a translation + "external-login-validation.review-account-info.table.header.current-value": "Current value", + + // "external-login-validation.review-account-info.table.header.action": "Override", + // TODO New key - Add a translation + "external-login-validation.review-account-info.table.header.action": "Override", } From ade1243f35954aba12f7650e8f6ef7725b179e8f Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 28 Sep 2023 18:30:19 +0200 Subject: [PATCH 19/56] [CST-10703] review account unit tests & other --- src/app/core/eperson/eperson-data.service.ts | 9 +- .../external-login-page.component.html | 11 +- .../external-login-page.component.ts | 53 ++-- .../email-validated.component.spec.ts | 11 +- .../email-validated.component.ts | 2 + ...ernal-login-validation-page.component.html | 13 +- ...al-login-validation-page.component.spec.ts | 105 +++++++- ...xternal-login-validation-page.component.ts | 33 ++- .../helpers/compare-values.pipe.ts | 9 +- .../review-account-info.component.html | 8 +- .../review-account-info.component.spec.ts | 216 +++++++++++++++- .../review-account-info.component.ts | 241 +++++++++++++----- .../models/registration-data.mock.model.ts | 65 +++-- .../services/external-login.service.ts | 16 +- src/assets/i18n/en.json5 | 12 + 15 files changed, 655 insertions(+), 149 deletions(-) diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index affbe6d045d..7622866833c 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -361,10 +361,15 @@ export class EPersonDataService extends IdentifiableDataService impleme * @param token registration-token * @param metadataKey metadata key of the metadata field that should be overriden */ - mergeEPersonDataWithToken(uuid: string, token: string, metadataKey: string): Observable> { + mergeEPersonDataWithToken(uuid: string, token: string, metadataKey?: string): Observable> { const requestId = this.requestService.generateRequestId(); const hrefObs = this.getBrowseEndpoint().pipe( - map((href: string) => `${href}/${uuid}?token=${token}&override=${metadataKey}`)); + map((href: string) => + hasValue(metadataKey) + ? `${href}/${uuid}?token=${token}&override=${metadataKey}` + : `${href}/${uuid}?token=${token}` + ) + ); hrefObs.pipe( find((href: string) => hasValue(href)), diff --git a/src/app/external-login-page/external-login-page.component.html b/src/app/external-login-page/external-login-page.component.html index 7aa4ad6e837..82c9419930b 100644 --- a/src/app/external-login-page/external-login-page.component.html +++ b/src/app/external-login-page/external-login-page.component.html @@ -1,4 +1,11 @@
- + + + + +
- diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index 91bd59ed3c9..8a41bc4a8ad 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -2,39 +2,62 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { hasValue } from '../shared/empty.util'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; -import { RemoteData } from '../core/data/remote-data'; import { Registration } from '../core/shared/registration.model'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; +import { AlertType } from '../shared/alert/aletr-type'; +import { Observable, map, of } from 'rxjs'; +import { getRemoteDataPayload } from '../core/shared/operators'; @Component({ templateUrl: './external-login-page.component.html', - styleUrls: ['./external-login-page.component.scss'] + styleUrls: ['./external-login-page.component.scss'], }) export class ExternalLoginPageComponent implements OnInit { - + /** + * The token used to get the registration data, + * retrieved from the url. + * @memberof ExternalLoginPageComponent + */ public token: string; - - public registrationData: RegistrationData = mockRegistrationDataModel; + /** + * The registration data of the user. + */ + public registrationData$: Observable = of( + mockRegistrationDataModel + ); + /** + * The type of alert to show. + */ + public AlertTypeEnum = AlertType; constructor( private epersonRegistrationService: EpersonRegistrationService, - private arouter: ActivatedRoute, + private arouter: ActivatedRoute ) { this.token = this.arouter.snapshot.queryParams.token; } ngOnInit(): void { + this.getRegistrationData(); + // TODO: remove this line (temporary) + // this.token = '1234567890'; + } + + /** + * Get the registration data of the user, + * based on the token. + */ + getRegistrationData() { if (hasValue(this.token)) { - this.epersonRegistrationService.searchByToken(this.token).subscribe((registration: RemoteData - ) => { - console.log('ExternalLoginPageComponent ngOnInit registration', registration); - if (registration.hasSucceeded) { - this.registrationData = Object.assign(new RegistrationData(), registration.payload); - console.log('ExternalLoginPageComponent ngOnInit registrationData', this.registrationData); - } - }); + this.registrationData$ = this.epersonRegistrationService + .searchByToken(this.token) + .pipe( + getRemoteDataPayload(), + map((registration: Registration) => + Object.assign(new RegistrationData(), registration) + ) + ); } } - } diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts index d5d2c2794cc..fee341ad22c 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts @@ -2,10 +2,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmailValidatedComponent } from './email-validated.component'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { of } from 'rxjs'; import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { Router } from '@angular/router'; +import { RouterStub } from 'src/app/shared/testing/router.stub'; +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; describe('EmailValidatedComponent', () => { let component: EmailValidatedComponent; @@ -24,6 +27,8 @@ describe('EmailValidatedComponent', () => { await TestBed.configureTestingModule({ declarations: [ EmailValidatedComponent ], providers: [ + { provide: AuthService, useValue: {}}, + { provide: Router, useValue: new RouterStub() }, { provide: TranslateService, useValue: translateServiceStub }, ], imports: [ @@ -35,7 +40,7 @@ describe('EmailValidatedComponent', () => { } }), ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts index 1ff85244386..c469b8ccbc1 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.ts @@ -9,6 +9,8 @@ import { AuthService } from '../../core/auth/auth.service'; }) export class EmailValidatedComponent { + // TODO: (temporary) + // evaluate if this is needed @Input() registrationToken: string; constructor(private authService: AuthService, private router: Router) { diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html index 227dc740988..3c9fa39d0b0 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.html +++ b/src/app/external-login-validation-page/external-login-validation-page.component.html @@ -1,11 +1,16 @@
- - - - + + + + +
diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts index 3ec5c77409f..155d64fe082 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts @@ -1,16 +1,68 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; +import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { Observable, of } from 'rxjs'; +import { RemoteData } from '../core/data/remote-data'; +import { CommonModule } from '@angular/common'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { AlertType } from '../shared/alert/aletr-type'; describe('ExternalLoginValidationPageComponent', () => { let component: ExternalLoginValidationPageComponent; let fixture: ComponentFixture; + let epersonRegistrationService: any; + + const registrationDataMock = { + registrationType: 'orcid', + email: 'test@test.com', + netId: '0000-0000-0000-0000', + user: 'a44d8c9e-9b1f-4e7f-9b1a-5c9d8a0b1f1a', + registrationMetadata: { + 'email': [{ value: 'test@test.com' }], + 'eperson.lastname': [{ value: 'Doe' }], + 'eperson.firstname': [{ value: 'John' }], + }, + }; + + const tokenMock = 'as552-5a5a5-5a5a5-5a5a5'; + + const routeStub = { + snapshot: { + queryParams: { + token: tokenMock + } + } + }; beforeEach(async () => { + epersonRegistrationService = { + searchByToken: (token: string): Observable> => { return createSuccessfulRemoteDataObject$(registrationDataMock); } + }; + await TestBed.configureTestingModule({ - declarations: [ ExternalLoginValidationPageComponent ] + declarations: [ExternalLoginValidationPageComponent], + providers: [ + { provide: ActivatedRoute, useValue: routeStub }, + { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + ], + schemas: [NO_ERRORS_SCHEMA], }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { @@ -22,4 +74,53 @@ describe('ExternalLoginValidationPageComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set the token from the query parameters', () => { + expect(component.token).toEqual(tokenMock); + }); + + it('should initialize the registration data', () => { + spyOn(epersonRegistrationService, 'searchByToken').and.callThrough(); + component.ngOnInit(); + expect(epersonRegistrationService.searchByToken).toHaveBeenCalledWith(tokenMock); + expect(component.registrationData$).toBeTruthy(); + }); + + it('should render ds-email-validated component when registrationData$ does not have an email', () => { + component.registrationData$ = of(Object.assign(new RegistrationData(), { registrationDataMock, email: null })); + component.ngOnInit(); + fixture.detectChanges(); + + const emailValidatedComponent = fixture.nativeElement.querySelector('ds-email-validated'); + const reviewAccountInfoComponent = fixture.nativeElement.querySelector('ds-review-account-info'); + + expect(emailValidatedComponent).toBeTruthy(); + expect(reviewAccountInfoComponent).toBeNull(); + }); + + it('should render ds-review-account-info component when registrationData$ has an email', () => { + component.registrationData$ = of(Object.assign(new RegistrationData(), { registrationDataMock, email: 'hey@hello.com' })); + // component.ngOnInit(); + fixture.detectChanges(); + const emailValidatedComponent = fixture.nativeElement.querySelector('ds-email-validated'); + const reviewAccountInfoComponent = fixture.nativeElement.querySelector('ds-review-account-info'); + + expect(emailValidatedComponent).toBeNull(); + expect(reviewAccountInfoComponent).toBeTruthy(); + }); + + it('should render ds-alert component when token is missing', () => { + component.token = null; + component.ngOnInit(); + fixture.detectChanges(); + + const alertComponent = fixture.nativeElement.querySelector('ds-alert'); + + expect(alertComponent).toBeTruthy(); + expect(component.AlertTypeEnum).toEqual(AlertType); + }); + + afterEach(() => { + fixture.destroy(); + }); }); diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts index 2f3be1c4e0c..4146ace1be2 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.ts @@ -1,53 +1,58 @@ import { Component, OnInit } from '@angular/core'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; -import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { ActivatedRoute } from '@angular/router'; import { hasValue } from '../shared/empty.util'; -import { RemoteData } from '../core/data/remote-data'; import { Registration } from '../core/shared/registration.model'; -import { Observable, map, of, tap } from 'rxjs'; +import { Observable, map, of } from 'rxjs'; import { getRemoteDataPayload } from '../core/shared/operators'; +import { AlertType } from '../shared/alert/aletr-type'; @Component({ templateUrl: './external-login-validation-page.component.html', styleUrls: ['./external-login-validation-page.component.scss'], }) export class ExternalLoginValidationPageComponent implements OnInit { - /** - * Whether or not the email address is already used by another user - */ - public emailExists: boolean; /** * The token used to get the registration data */ public token: string; + /** + * The type of alert to show + */ + public AlertTypeEnum = AlertType; + /** * The registration data of the user */ - public registrationData$: Observable = of( - mockRegistrationDataModel - ); + public registrationData$: Observable + // = of( + // mockRegistrationDataModel + // ); constructor( private epersonRegistrationService: EpersonRegistrationService, private arouter: ActivatedRoute ) { this.token = this.arouter.snapshot.queryParams.token; - this.emailExists = true; } ngOnInit(): void { // -> if email address is not used by other user => Email Validated component // -> if email address is used by other user => Review account information component + this.getRegistrationData(); + // TODO: remove this line (temporary) + // this.token = '1234567890'; + } + /** + * Get the registration data from the token + */ + getRegistrationData() { if (hasValue(this.token)) { this.registrationData$ = this.epersonRegistrationService .searchByToken(this.token) .pipe( - tap((registration: RemoteData) => { - this.emailExists = hasValue(registration.payload.email); - }), getRemoteDataPayload(), map((registration: Registration) => Object.assign(new RegistrationData(), registration) diff --git a/src/app/external-login-validation-page/helpers/compare-values.pipe.ts b/src/app/external-login-validation-page/helpers/compare-values.pipe.ts index aefbe9a0ae0..ff5fb906b5b 100644 --- a/src/app/external-login-validation-page/helpers/compare-values.pipe.ts +++ b/src/app/external-login-validation-page/helpers/compare-values.pipe.ts @@ -5,7 +5,14 @@ import { Pipe, PipeTransform } from '@angular/core'; }) export class CompareValuesPipe implements PipeTransform { - transform(receivedValue: string, currentValue: string): unknown { + /** + * Returns a string with a checkmark if the received value is equal to the current value, + * or the current value if they are not equal. + * @param receivedValue the value received from the registration data + * @param currentValue the value from the current user + * @returns the value to be displayed in the template + */ + transform(receivedValue: string, currentValue: string): string { if (receivedValue === currentValue) { return ''; } else { diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html index 98a01d734ee..82aec84441e 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.html @@ -41,9 +41,9 @@

{{'external-login-validation.review-account-info.header' | translate}}

@@ -52,7 +52,7 @@

{{'external-login-validation.review-account-info.header' | translate}}

-
diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts index 7cd24cbeec2..7e1d3f08de3 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts @@ -1,25 +1,233 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { ReviewAccountInfoComponent } from './review-account-info.component'; +import { + TranslateLoader, + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { Observable, Subscription, of } from 'rxjs'; +import { RemoteData } from '../../core/data/remote-data'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { EPersonMock } from '../../shared/testing/eperson.mock'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; +import { EventEmitter } from '@angular/core'; +import { CompareValuesPipe } from '../helpers/compare-values.pipe'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; let fixture: ComponentFixture; + let ePersonDataServiceStub: any; + let router: any; + let notificationsService: any; + + const translateServiceStub = { + get: () => of('test-message'), + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + const mockEPerson = EPersonMock; + const modalStub = { + open: () => ({ componentInstance: { response: of(true) }}), + close: () => null, + dismiss: () => null, + }; + const registrationDataMock = { + registrationType: 'orcid', + email: 'test@test.com', + netId: '0000-0000-0000-0000', + user: 'a44d8c9e-9b1f-4e7f-9b1a-5c9d8a0b1f1a', + registrationMetadata: { + 'email': [{ value: 'test@test.com' }], + 'eperson.lastname': [{ value: 'Doe' }], + 'eperson.firstname': [{ value: 'John' }], + }, + }; beforeEach(async () => { + ePersonDataServiceStub = { + findById(uuid: string): Observable> { + return createSuccessfulRemoteDataObject$(mockEPerson); + }, + mergeEPersonDataWithToken( + token: string, + metadata?: string + ): Observable> { + return createSuccessfulRemoteDataObject$(mockEPerson); + }, + }; + router = new RouterMock(); + notificationsService = new NotificationsServiceStub(); await TestBed.configureTestingModule({ - declarations: [ ReviewAccountInfoComponent ] - }) - .compileComponents(); + declarations: [ReviewAccountInfoComponent, CompareValuesPipe], + providers: [ + { provide: EPersonDataService, useValue: ePersonDataServiceStub }, + { provide: NgbModal, useValue: modalStub }, + { + provide: NotificationsService, + useValue: notificationsService, + }, + { provide: TranslateService, useValue: translateServiceStub }, + { provide: Router, useValue: router }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + ], + }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ReviewAccountInfoComponent); component = fixture.componentInstance; + component.registrationData = Object.assign( + new RegistrationData(), + registrationDataMock + ); + component.registrationToken = 'test-token'; + spyOn(router, 'navigate'); fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should call getEPersonData when ngOnInit is called', () => { + spyOn(component, 'getEPersonData'); + component.ngOnInit(); + expect(component.getEPersonData).toHaveBeenCalled(); + }); + + it('should prepare data to compare', () => { + component.ngOnInit(); + const dataToCompare = component.dataToCompare; + expect(dataToCompare.length).toBe(3); + expect(dataToCompare[0].label).toBe('email'); + expect(dataToCompare[1].label).toBe('lastname'); + expect(dataToCompare[2].label).toBe('firstname'); + expect(dataToCompare[0].overrideValue).toBe(false); + expect(dataToCompare[0].receivedValue).toBe('test@test.com'); + }); + + it('should get EPerson data', fakeAsync(() => { + spyOn(ePersonDataServiceStub, 'findById').and.returnValue( + of({ payload: mockEPerson } as RemoteData) + ); + component.getEPersonData(); + tick(); + expect(ePersonDataServiceStub.findById).toHaveBeenCalledWith(registrationDataMock.user); + expect(component.epersonCurrentData).toEqual(EPersonMock); + })); + + it('should update dataToCompare when overrideValue is changed', () => { + component.onOverrideChange(true, 'email'); + expect(component.dataToCompare[0].overrideValue).toBe(true); + }); + + it('should open a confirmation modal on onSave and confirm', fakeAsync(() => { + spyOn(modalStub, 'open').and.returnValue({ + componentInstance: { response: of(true) }, + }); + spyOn(component, 'mergeEPersonDataWithToken'); + component.onSave(); + tick(); + expect(modalStub.open).toHaveBeenCalled(); + expect(component.mergeEPersonDataWithToken).toHaveBeenCalled(); + })); + + it('should open a confirmation modal on onSave and cancel', fakeAsync(() => { + spyOn(modalStub, 'open').and.returnValue({ + componentInstance: { response: of(false) }, + }); + spyOn(component, 'mergeEPersonDataWithToken'); + component.onSave(); + tick(); + expect(modalStub.open).toHaveBeenCalled(); + expect(component.mergeEPersonDataWithToken).not.toHaveBeenCalled(); + })); + + it('should merge EPerson data with token when overrideValue is true', fakeAsync(() => { + component.dataToCompare[0].overrideValue = true; + spyOn(ePersonDataServiceStub, 'mergeEPersonDataWithToken').and.returnValue( + of({ hasSucceeded: true }) + ); + component.mergeEPersonDataWithToken(); + tick(); + expect(ePersonDataServiceStub.mergeEPersonDataWithToken).toHaveBeenCalledTimes(1); + expect(router.navigate).toHaveBeenCalledWith(['/profile']); + })); + + it('should merge EPerson data with token when overrideValue is false', fakeAsync(() => { + spyOn(ePersonDataServiceStub, 'mergeEPersonDataWithToken').and.returnValue( + of({ hasSucceeded: true }) + ); + component.mergeEPersonDataWithToken(); + tick(); + expect(ePersonDataServiceStub.mergeEPersonDataWithToken).toHaveBeenCalledTimes(1); + expect(router.navigate).toHaveBeenCalledWith(['/profile']); + })); + + + it('should unsubscribe from subscriptions when ngOnDestroy is called', () => { + const subscription1 = jasmine.createSpyObj('Subscription', [ + 'unsubscribe', + ]); + const subscription2 = jasmine.createSpyObj('Subscription', [ + 'unsubscribe', + ]); + component.subs = [subscription1, subscription2]; + component.ngOnDestroy(); + expect(subscription1.unsubscribe).toHaveBeenCalled(); + expect(subscription2.unsubscribe).toHaveBeenCalled(); + }); + + it('should display registration data', () => { + const registrationTypeElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child th'); + const netIdElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child td'); + + expect(registrationTypeElement.textContent.trim()).toBe(registrationDataMock.registrationType.toUpperCase()); + expect(netIdElement.textContent.trim()).toBe(registrationDataMock.netId); + }); + + it('should display dataToCompare rows with translated labels and values', () => { + const dataRows: NodeListOf = fixture.nativeElement.querySelectorAll('tbody tr:not(:first-child)'); + // Assuming there are 3 dataToCompare rows based on the registrationDataMock + expect(dataRows.length).toBe(3); + // Assuming the first row is the email row abd the second row is the lastname row + const firstDataRow = dataRows[1]; + const firstDataLabel: HTMLElement = firstDataRow.querySelector('th'); + const firstDataReceivedValue: HTMLElement = firstDataRow.querySelectorAll('td')[0]; + const firstDataOverrideSwitch: HTMLElement = firstDataRow.querySelector('ui-switch'); + + expect(firstDataLabel.textContent.trim()).toBe('Lastname'); + expect(firstDataReceivedValue.textContent.trim()).toBe('Doe'); + expect(firstDataOverrideSwitch).not.toBeNull(); + }); + + it('should trigger onSave() when the button is clicked', () => { + spyOn(component, 'onSave'); + const saveButton: HTMLButtonElement = fixture.nativeElement.querySelector('button.btn-primary'); + saveButton.click(); + expect(component.onSave).toHaveBeenCalled(); + }); + + afterEach(() => { + fixture.destroy(); + }); }); diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts index 3d244a6294d..80e9e2a1237 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts @@ -3,15 +3,24 @@ import { ChangeDetectionStrategy, OnInit, Input, + OnDestroy, } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { EPersonMock } from '../../shared/testing/eperson.mock'; import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; -import { filter, from, switchMap, take } from 'rxjs'; +import { Observable, Subscription, filter, from, switchMap, take } from 'rxjs'; import { RemoteData } from 'src/app/core/data/remote-data'; import { ConfirmationModalComponent } from 'src/app/shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { hasValue } from 'src/app/shared/empty.util'; +import { + getFirstCompletedRemoteData, + getRemoteDataPayload, +} from 'src/app/core/shared/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; +import { Router } from '@angular/router'; export interface ReviewAccountInfoData { label: string; @@ -27,40 +36,180 @@ export interface ReviewAccountInfoData { styleUrls: ['./review-account-info.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReviewAccountInfoComponent implements OnInit { +export class ReviewAccountInfoComponent implements OnInit, OnDestroy { + /** + * The registration token sent from validation link + */ @Input() registrationToken: string; - + /** + * User data from the registration token + */ @Input() registrationData: RegistrationData; - epersonData: EPerson = EPersonMock; - + /** + * Text to display when the value is not applicable + */ notApplicableText = 'N/A'; - + /** + * List of data to compare + */ dataToCompare: ReviewAccountInfoData[] = []; + /** + * List of subscriptions + */ + subs: Subscription[] = []; + /** + * Current eperson data from the database, + * so we can compare it with the data from the registration token + */ + epersonCurrentData: EPerson;// = EPersonMock; constructor( private ePersonService: EPersonDataService, - private modalService: NgbModal + private modalService: NgbModal, + private notificationService: NotificationsService, + private translateService: TranslateService, + private router: Router ) { - // GET data from url validation link and display + // GET data from url validation link // Based on the URL data we get // 1. token - // 1. login user + // User should be automatically logged in // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 - // the header in the review page must show that the user is logged in + // How to handle the case when the email is not part of metadata + // and we have `eperson.orcid` in metadata list } ngOnInit(): void { - this.dataToCompare = this.prepareDataToCompare(); + this.getEPersonData(); + // TODO: remove after having data + // this.dataToCompare = this.prepareDataToCompare(); + } + + /** + * Get the current eperson data from the database. + * If the eperson is found, prepare the data to compare. + */ + getEPersonData() { + if ( + hasValue(this.registrationData) && + hasValue(this.registrationData.user) + ) { + this.ePersonService + .findById(this.registrationData.user) + .pipe(getFirstCompletedRemoteData(), getRemoteDataPayload()) + .subscribe((eperson: EPerson) => { + if (eperson) { + this.epersonCurrentData = eperson; + this.dataToCompare = this.prepareDataToCompare(); + } + }); + } + } + + /** + * Find the data to compare based on the metadata key and update the override value + * @param value value of the override checkbox + * @param identifier the metadata key + */ + public onOverrideChange(value: boolean, identifier: string) { + this.dataToCompare.find( + (data) => data.identifier === identifier + ).overrideValue = value; + } + + /** + * Open a confirmation modal to confirm the override of the data + * If confirmed, merge the data from the registration token with the data from the eperson + */ + public onSave() { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.headerLabel = + 'confirmation-modal.review-account-info.header'; + modalRef.componentInstance.infoLabel = + 'confirmation-modal.review-account-info.info'; + modalRef.componentInstance.cancelLabel = + 'confirmation-modal.review-account-info.cancel'; + modalRef.componentInstance.confirmLabel = + 'confirmation-modal.review-account-info.confirm'; + modalRef.componentInstance.brandColor = 'primary'; + modalRef.componentInstance.confirmIcon = 'fa fa-check'; + + this.subs.push( + modalRef.componentInstance.response + .pipe(take(1)) + .subscribe((confirm: boolean) => { + if (confirm) { + this.mergeEPersonDataWithToken(); + } + }) + ); + } + + /** + * Merge the data from the registration token with the data from the eperson. + * If any of the metadata is overridden, sent a merge request for each metadata to override. + * If none of the metadata is overridden, sent a merge request with the registration token only. + */ + mergeEPersonDataWithToken() { + let override$: Observable>; + if (this.dataToCompare.some((d) => d.overrideValue)) { + override$ = from(this.dataToCompare).pipe( + filter((data: ReviewAccountInfoData) => data.overrideValue), + switchMap((data: ReviewAccountInfoData) => { + return this.ePersonService.mergeEPersonDataWithToken( + this.epersonCurrentData.id, + this.registrationToken, + data.identifier + ); + }) + ); + } else { + override$ = this.ePersonService.mergeEPersonDataWithToken( + this.epersonCurrentData.id, + this.registrationToken + ); + } + this.subs.push( + override$.subscribe((response: RemoteData) => { + // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 + // redirect to profile page + if (response.hasSucceeded) { + console.log('mergeEPersonDataWithToken', response.payload); + this.notificationService.success( + this.translateService.get( + 'review-account-info.merge-data.notification.success' + ) + ); + this.router.navigate(['/profile']); + } + + if (response.hasFailed) { + this.notificationService.success( + this.translateService.get( + 'review-account-info.merge-data.notification.error' + ) + ); + } + }) + ); } - private prepareDataToCompare() { + /** + * Prepare the data to compare and display: + * -> For each metadata from the registration token, get the current value from the eperson. + * -> Label is the metadata key without the prefix e.g `eperson.` + * -> Identifier is the metadata key with the prefix e.g `eperson.lastname` + * -> Override value is false by default + * @returns List of data to compare + */ + private prepareDataToCompare(): ReviewAccountInfoData[] { const dataToCompare: ReviewAccountInfoData[] = []; Object.entries(this.registrationData.registrationMetadata).forEach( ([key, value]) => { console.log(key, value); dataToCompare.push({ - label: key.split('.')?.[1], + label: key.split('.')?.[1] ?? key.split('.')?.[0], currentValue: this.getCurrentValue(key), receivedValue: value[0].value, overrideValue: false, @@ -72,63 +221,19 @@ export class ReviewAccountInfoComponent implements OnInit { return dataToCompare; } - getEPersonData() { - // this.epersonData$ = this.ePersonService.findById() - // .pipe( - // getFirstCompletedRemoteData(), - // getRemoteDataPayload() - // ); - } - - private getCurrentValue(metadata: string): string { - return this.epersonData.firstMetadataValue(metadata); - } - - public onOverrideChange(value: boolean, identifier: string) { - this.dataToCompare.find( - (data) => data.identifier === identifier - ).overrideValue = value; - } - /** - * Merge the data from the registration token with the data from the eperson + * Return the current value of the metadata key from the eperson + * @param metadata metadata key + * @returns the current value of the metadata key from the eperson */ - public mergeEPersonRegistrationData() { - const modalRef = this.modalService.open(ConfirmationModalComponent); - modalRef.componentInstance.headerLabel = 'confirmation-modal.review-account-info.header'; - modalRef.componentInstance.infoLabel = 'confirmation-modal.review-account-info.info'; - modalRef.componentInstance.cancelLabel = 'confirmation-modal.review-account-info.cancel'; - modalRef.componentInstance.confirmLabel = 'confirmation-modal.review-account-info.confirm'; - modalRef.componentInstance.brandColor = 'primary'; - modalRef.componentInstance.confirmIcon = 'fa fa-check'; - modalRef.componentInstance.response - .pipe(take(1)) - .subscribe((confirm: boolean) => { - if (confirm) { - from(this.dataToCompare) - .pipe( - // what happens when is not overriden? - filter((data: ReviewAccountInfoData) => data.overrideValue), - switchMap((data: ReviewAccountInfoData) => { - return this.ePersonService.mergeEPersonDataWithToken( - this.epersonData.id, - this.registrationToken, - data.identifier - ); - }) - ) - .subscribe((response: RemoteData) => { - // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 - // redirect to profile page - if (response.hasSucceeded) { - console.log(response.payload); - } + private getCurrentValue(metadata: string): string { + if (metadata === 'email') { + return this.epersonCurrentData.email; + } + return this.epersonCurrentData.firstMetadataValue(metadata) ?? ''; + } - if (response.hasFailed) { - console.log(response.errorMessage); - } - }); - } - }); + ngOnDestroy(): void { + this.subs.filter((s) => hasValue(s)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts index 8d02f349b05..961d443a665 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -2,30 +2,43 @@ import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; import { RegistrationData } from './registration-data.model'; import { MetadataValue } from '../../../core/shared/metadata.models'; -export const mockRegistrationDataModel: RegistrationData = Object.assign( new RegistrationData(), { - id: '3', - email: 'user@institution.edu', - user: '028dcbb8-0da2-4122-a0ea-254be49ca107', - registrationType: AuthMethodType.Orcid, - netId: '0000-1111-2222-3333', - registrationMetadata: { - 'eperson.firstname': [ - Object.assign(new MetadataValue(), { - value: 'User', - language: null, - authority: '', - confidence: -1, - place: -1, - }) - ], - 'eperson.lastname': [ - Object.assign(new MetadataValue(), { - value: 'Power', - language: null, - authority: '', - confidence: -1, - place: -1 - }) - ] +export const mockRegistrationDataModel: RegistrationData = Object.assign( + new RegistrationData(), + { + id: '3', + email: 'user@institution.edu', + user: '028dcbb8-0da2-4122-a0ea-254be49ca107', + registrationType: AuthMethodType.Orcid, + netId: '0000-1111-2222-3333', + registrationMetadata: { + 'eperson.firstname': [ + Object.assign(new MetadataValue(), { + value: 'User 1', + language: null, + authority: '', + confidence: -1, + place: -1, + }), + ], + 'eperson.lastname': [ + Object.assign(new MetadataValue(), { + value: 'Power', + language: null, + authority: '', + confidence: -1, + place: -1, + }), + ], + 'email': [ + { + value: 'power-user@orcid.org', + language: null, + authority: '', + confidence: -1, + place: -1, + overrides: 'power-user@dspace.org', + }, + ], + }, } -}); +); diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index f0289eb5b78..f757bb2bdbf 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -5,6 +5,7 @@ import { EpersonRegistrationService } from '../../../core/data/eperson-registrat import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { NotificationsService } from '../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; @Injectable({ providedIn: 'root' @@ -14,10 +15,18 @@ export class ExternalLoginService { constructor( private epersonRegistrationService: EpersonRegistrationService, private router: Router, - private notificationService: NotificationsService + private notificationService: NotificationsService, + private translate: TranslateService ) { } - + /** + * Update the registration data + * @param values the values to update or add + * @param field the filed to be updated + * @param registrationId the registration id + * @param token the registration token + * @param operation operation to be performed + */ patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable> { const updatedValues = values.map((value) => value); return this.epersonRegistrationService.patchUpdateRegistration(updatedValues, field, registrationId, token, operation).pipe( @@ -27,8 +36,7 @@ export class ExternalLoginService { this.router.navigate(['/email-confirmation']); } if (rd.hasFailed) { - console.log('Email update failed: email address was omitted or the operation is not valid', rd.errorMessage); - this.notificationService.error('Something went wrong.Email address was omitted or the operation is not valid'); + this.notificationService.error(this.translate.get('external-login-page.provide-email.notifications.error')); } return rd; }) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 328296818c4..9d3161f0aaf 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7213,4 +7213,16 @@ "external-login-validation.review-account-info.table.header.current-value": "Current value", "external-login-validation.review-account-info.table.header.action": "Override", + + "on-label": "ON", + + "off-label": "OFF", + + "review-account-info.merge-data.notification.success": "Your account information has been updated successfully", + + "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", + + "external-login.validate-email.no-token": "The validation link is no longer valid. Please try again.", + + "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", } From 0397f3c3bd602711b701a7ab86ea1dd335f630c2 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 29 Sep 2023 17:56:11 +0200 Subject: [PATCH 20/56] [CST-10703] component rearrangements & validate email logic --- src/app/app-routing.module.ts | 5 + .../external-login-page.component.ts | 2 +- ...review-account-info-page-routing.module.ts | 16 +++ ...in-review-account-info-page.component.html | 13 +++ ...in-review-account-info-page.component.scss | 0 ...review-account-info-page.component.spec.ts | 25 +++++ ...ogin-review-account-info-page.component.ts | 65 +++++++++++ ...l-login-review-account-info-page.module.ts | 27 +++++ .../helpers/compare-values.pipe.ts | 0 .../review-account-info.component.html | 0 .../review-account-info.component.scss | 0 .../review-account-info.component.spec.ts | 0 .../review-account-info.component.ts | 1 - ...ogin-review-account-info-page.component.ts | 25 +++++ .../email-validated.component.ts | 8 +- ...ernal-login-validation-page.component.html | 12 +- ...xternal-login-validation-page.component.ts | 103 +++++++++++++----- .../external-login-validation-page.module.ts | 8 +- .../provide-email.component.html | 4 +- .../external-log-in.component.html | 6 +- .../external-log-in.component.ts | 19 ++-- .../models/registration-data.mock.model.ts | 2 +- .../services/external-login.service.ts | 1 + src/assets/i18n/en.json5 | 2 +- 24 files changed, 276 insertions(+), 68 deletions(-) create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page.component.html create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page.component.scss create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts create mode 100644 src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts rename src/app/{external-login-validation-page => external-login-review-account-info}/helpers/compare-values.pipe.ts (100%) rename src/app/{external-login-validation-page => external-login-review-account-info}/review-account-info/review-account-info.component.html (100%) rename src/app/{external-login-validation-page => external-login-review-account-info}/review-account-info/review-account-info.component.scss (100%) rename src/app/{external-login-validation-page => external-login-review-account-info}/review-account-info/review-account-info.component.spec.ts (100%) rename src/app/{external-login-validation-page => external-login-review-account-info}/review-account-info/review-account-info.component.ts (99%) create mode 100644 src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d5f3a5aecdc..2fd18766af9 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -181,6 +181,11 @@ import { RedirectService } from './redirect/redirect.service'; loadChildren: () => import('./external-login-validation-page/external-login-validation-page.module') .then((m) => m.ExternalLoginValidationPageModule) }, + { + path: 'review-account', + loadChildren: () => import('./external-login-review-account-info/external-login-review-account-info-page.module') + .then((m) => m.ExternalLoginReviewAccountInfoModule) + }, { path: 'email-confirmation', loadChildren: () => import('./external-login-email-confirmation-page/external-login-email-confirmation-page.module') diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index 8a41bc4a8ad..b98e823fc90 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -41,7 +41,7 @@ export class ExternalLoginPageComponent implements OnInit { ngOnInit(): void { this.getRegistrationData(); // TODO: remove this line (temporary) - // this.token = '1234567890'; + this.token = '1234567890'; } /** diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts new file mode 100644 index 00000000000..94f2ef42664 --- /dev/null +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + component: ExternalLoginReviewAccountInfoPageComponent, +},]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class ExternalLoginReviewAccountInfoRoutingModule { } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html new file mode 100644 index 00000000000..6d5defa2ca4 --- /dev/null +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html @@ -0,0 +1,13 @@ +
+ + + + +
diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.scss b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts new file mode 100644 index 00000000000..ef2dfe82017 --- /dev/null +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; + +describe('ExternalLoginReviewAccountInfoPageComponent', () => { + let component: ExternalLoginReviewAccountInfoPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExternalLoginReviewAccountInfoPageComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalLoginReviewAccountInfoPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts new file mode 100644 index 00000000000..8b02a347c51 --- /dev/null +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { AlertType } from '../shared/alert/aletr-type'; +import { Observable, map } from 'rxjs'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { ActivatedRoute } from '@angular/router'; +import { hasValue } from '../shared/empty.util'; +import { getRemoteDataPayload } from '../core/shared/operators'; +import { Registration } from '../core/shared/registration.model'; + +@Component({ + templateUrl: './external-login-review-account-info-page.component.html', + styleUrls: ['./external-login-review-account-info-page.component.scss'] +}) +export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { + /** + * The token used to get the registration data + */ + public token: string; + + /** + * The type of alert to show + */ + public AlertTypeEnum = AlertType; + + /** + * The registration data of the user + */ + public registrationData$: Observable; + // = of( + // mockRegistrationDataModel + // ); + + constructor( + private epersonRegistrationService: EpersonRegistrationService, + private arouter: ActivatedRoute + ) { + this.token = this.arouter.snapshot.queryParams.token; + } + + + ngOnInit(): void { + // -> if email address is not used by other user => Email Validated component + // -> if email address is used by other user => Review account information component + this.getRegistrationData(); + // TODO: remove this line (temporary) + // this.token = '1234567890'; + } + /** + * Get the registration data from the token + */ + getRegistrationData() { + if (hasValue(this.token)) { + this.registrationData$ = this.epersonRegistrationService + .searchByToken(this.token) + .pipe( + getRemoteDataPayload(), + map((registration: Registration) => + Object.assign(new RegistrationData(), registration) + ) + ); + } + } + +} diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts new file mode 100644 index 00000000000..368f988a829 --- /dev/null +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ExternalLoginReviewAccountInfoRoutingModule } from './external-login-review-account-info-page-routing.module'; +import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; +import { CompareValuesPipe } from './helpers/compare-values.pipe'; +import { ThemedExternalLoginReviewAccountInfoPageComponent } from './themed-external-login-review-account-info-page.component'; +import { ReviewAccountInfoComponent } from './review-account-info/review-account-info.component'; +import { UiSwitchModule } from 'ngx-ui-switch'; +import { SharedModule } from '../shared/shared.module'; + + +@NgModule({ + declarations: [ + ExternalLoginReviewAccountInfoPageComponent, + CompareValuesPipe, + ThemedExternalLoginReviewAccountInfoPageComponent, + ReviewAccountInfoComponent + ], + imports: [ + CommonModule, + ExternalLoginReviewAccountInfoRoutingModule, + SharedModule, + UiSwitchModule, + ] +}) +export class ExternalLoginReviewAccountInfoModule { } diff --git a/src/app/external-login-validation-page/helpers/compare-values.pipe.ts b/src/app/external-login-review-account-info/helpers/compare-values.pipe.ts similarity index 100% rename from src/app/external-login-validation-page/helpers/compare-values.pipe.ts rename to src/app/external-login-review-account-info/helpers/compare-values.pipe.ts diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.html b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html similarity index 100% rename from src/app/external-login-validation-page/review-account-info/review-account-info.component.html rename to src/app/external-login-review-account-info/review-account-info/review-account-info.component.html diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.scss b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.scss similarity index 100% rename from src/app/external-login-validation-page/review-account-info/review-account-info.component.scss rename to src/app/external-login-review-account-info/review-account-info/review-account-info.component.scss diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts similarity index 100% rename from src/app/external-login-validation-page/review-account-info/review-account-info.component.spec.ts rename to src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts diff --git a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts similarity index 99% rename from src/app/external-login-validation-page/review-account-info/review-account-info.component.ts rename to src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index 80e9e2a1237..93952c2a26f 100644 --- a/src/app/external-login-validation-page/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -7,7 +7,6 @@ import { } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { EPersonMock } from '../../shared/testing/eperson.mock'; import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; import { Observable, Subscription, filter, from, switchMap, take } from 'rxjs'; import { RemoteData } from 'src/app/core/data/remote-data'; diff --git a/src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts new file mode 100644 index 00000000000..53139bb7811 --- /dev/null +++ b/src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../shared/theme-support/themed.component'; +import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; + +/** + * Themed wrapper for ExternalLoginReviewAccountInfoPageComponent + */ +@Component({ + selector: 'ds-themed-external-login-page', + styleUrls: [], + templateUrl: './../shared/theme-support/themed.component.html' +}) +export class ThemedExternalLoginReviewAccountInfoPageComponent extends ThemedComponent { + protected getComponentName(): string { + return 'ExternalLoginReviewAccountInfoPageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../themes/${themeName}/app/external-login-review-account-info/external-login-review-account-info-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./external-login-review-account-info-page.component`); + } +} diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts index c469b8ccbc1..4147dd23720 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.ts @@ -14,12 +14,6 @@ export class EmailValidatedComponent { @Input() registrationToken: string; constructor(private authService: AuthService, private router: Router) { - // if user is logged in, redirect to home page - // in case user logs in with an existing account - this.authService.isAuthenticated().subscribe((isAuthenticated: boolean) => { - if (isAuthenticated) { - this.router.navigate(['/']); - } - }); + this.authService.setRedirectUrl('/review-account'); } } diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html index 3c9fa39d0b0..3efd1ce0e38 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.html +++ b/src/app/external-login-validation-page/external-login-validation-page.component.html @@ -1,15 +1,9 @@
- - - - + - + diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts index 4146ace1be2..7a20473ddbe 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.ts @@ -1,18 +1,25 @@ -import { Component, OnInit } from '@angular/core'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { Component } from '@angular/core'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { ActivatedRoute } from '@angular/router'; -import { hasValue } from '../shared/empty.util'; -import { Registration } from '../core/shared/registration.model'; -import { Observable, map, of } from 'rxjs'; -import { getRemoteDataPayload } from '../core/shared/operators'; import { AlertType } from '../shared/alert/aletr-type'; +import { hasNoValue, hasValue } from '../shared/empty.util'; +import { getRemoteDataPayload } from '../core/shared/operators'; +import { BehaviorSubject, Observable, map, of, switchMap, tap } from 'rxjs'; +import { Registration } from '../core/shared/registration.model'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { RemoteData } from '../core/data/remote-data'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { EPersonDataService } from '../core/eperson/eperson-data.service'; +import { MetadataValue } from '../core/shared/metadata.models'; +import { EPerson } from '../core/eperson/models/eperson.model'; +import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; @Component({ templateUrl: './external-login-validation-page.component.html', styleUrls: ['./external-login-validation-page.component.scss'], }) -export class ExternalLoginValidationPageComponent implements OnInit { +export class ExternalLoginValidationPageComponent { /** * The token used to get the registration data */ @@ -23,41 +30,81 @@ export class ExternalLoginValidationPageComponent implements OnInit { */ public AlertTypeEnum = AlertType; - /** - * The registration data of the user - */ - public registrationData$: Observable - // = of( - // mockRegistrationDataModel - // ); + private validationFailed: BehaviorSubject = + new BehaviorSubject(false); constructor( private epersonRegistrationService: EpersonRegistrationService, - private arouter: ActivatedRoute + private arouter: ActivatedRoute, + private epersonDataService: EPersonDataService, + private notificationService: NotificationsService, + private translateService: TranslateService ) { this.token = this.arouter.snapshot.queryParams.token; + this.token = '1234567890'; // TODO: remove this line (temporary) } ngOnInit(): void { - // -> if email address is not used by other user => Email Validated component - // -> if email address is used by other user => Review account information component - this.getRegistrationData(); - // TODO: remove this line (temporary) - // this.token = '1234567890'; + // TODO: Uncomment this line later + // this.getRegistrationData(); } + + public hasFailed(): Observable { + return this.validationFailed.asObservable(); + } + /** * Get the registration data from the token */ getRegistrationData() { + this.validationFailed.next(true); + if (hasValue(this.token)) { - this.registrationData$ = this.epersonRegistrationService - .searchByToken(this.token) - .pipe( - getRemoteDataPayload(), - map((registration: Registration) => - Object.assign(new RegistrationData(), registration) - ) - ); + this.fetchRegistrationDataAndCreateUser(this.token); } } + + fetchRegistrationDataAndCreateUser(token: string) { + this.epersonRegistrationService + .searchByToken(token) + .pipe( + switchMap((rd) => { + if (hasValue(rd.payload) && hasNoValue(rd.payload.user)) { + const registrationData = Object.assign( + new RegistrationData(), + rd.payload + ); + return this.createUserFromToken(token, registrationData); + } else { + return of(rd); + } + }) + ) + .subscribe((rd: RemoteData) => { + if (rd.hasFailed) { + this.validationFailed.next(true); + } + }); + } + + createUserFromToken(token: string, registrationData: RegistrationData) { + const metadataValues = Object.entries(registrationData.registrationMetadata) + .filter(([key, value]) => hasValue(value[0]?.value)) + .map(([key, value]) => ({ + key, + value: value[0]?.value, + })); + const eperson = Object.assign(new EPerson(), { + metadata: metadataValues, + canLogIn: true, + requireCertificate: false, + }); + return this.epersonDataService.createEPersonForToken(eperson, token).pipe( + tap((rd: RemoteData) => { + if (rd.hasFailed) { + this.validationFailed.next(true); + } + }) + ); + } } diff --git a/src/app/external-login-validation-page/external-login-validation-page.module.ts b/src/app/external-login-validation-page/external-login-validation-page.module.ts index ba41cf95a1b..3bad614d7e6 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.module.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.module.ts @@ -5,26 +5,20 @@ import { ExternalLoginValidationPageRoutingModule } from './external-login-valid import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; -import { UiSwitchModule } from 'ngx-ui-switch'; -import { ReviewAccountInfoComponent } from './review-account-info/review-account-info.component'; import { EmailValidatedComponent } from './email-validated/email-validated.component'; import { SharedModule } from '../shared/shared.module'; -import { CompareValuesPipe } from './helpers/compare-values.pipe'; @NgModule({ declarations: [ ExternalLoginValidationPageComponent, ThemedExternalLoginValidationPageComponent, - ReviewAccountInfoComponent, EmailValidatedComponent, - CompareValuesPipe ], imports: [ CommonModule, ExternalLoginValidationPageRoutingModule, - SharedModule, - UiSwitchModule, + SharedModule ] }) export class ExternalLoginValidationPageModule { } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html index b5a2efa7b68..46e804e1c2e 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html @@ -17,7 +17,7 @@

emailForm.get('email').hasError('required') && emailForm.get('email').touched " - class="error-message" + class="text-danger" > {{ "external-login.confirmation.email-required" | translate }}

@@ -26,7 +26,7 @@

emailForm.get('email').hasError('email') && emailForm.get('email').touched " - class="error-message" + class="text-danger" > {{ "external-login.confirmation.email-invalid" | translate }}

diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html index 59219922470..47a301501bf 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -36,14 +36,16 @@
diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts index 3b82ab70ca1..2ef5df0cae1 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -57,15 +57,7 @@ export class ExternalLogInComponent implements OnInit { private modalService: NgbModal, private authService: AuthService, private router: Router - ) { - // if user is logged in, redirect to home page - // in case user logs in with an existing account - this.authService.isAuthenticated().subscribe((isAuthenticated: boolean) => { - if (isAuthenticated) { - this.router.navigate(['/']); - } - }); - } + ) {} /** * Provide the registration data object to the objectInjector. @@ -124,6 +116,15 @@ export class ExternalLogInComponent implements OnInit { openLoginModal(content: any) { this.modalRef = this.modalService.open(content); + this.authService.setRedirectUrl('/review-account'); + + this.modalRef.dismissed.subscribe(() => { + this.clearRedirectUrl(); + }); + } + + clearRedirectUrl() { + this.authService.clearRedirectUrl(); } ngOnDestroy(): void { diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts index 961d443a665..9ef3d4162c1 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -6,7 +6,7 @@ export const mockRegistrationDataModel: RegistrationData = Object.assign( new RegistrationData(), { id: '3', - email: 'user@institution.edu', + email: null,//'user@institution.edu', user: '028dcbb8-0da2-4122-a0ea-254be49ca107', registrationType: AuthMethodType.Orcid, netId: '0000-1111-2222-3333', diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index f757bb2bdbf..4f60d9e5d25 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -29,6 +29,7 @@ export class ExternalLoginService { */ patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable> { const updatedValues = values.map((value) => value); + this.router.navigate(['/email-confirmation']); // TODO: remove this line (temporary) return this.epersonRegistrationService.patchUpdateRegistration(updatedValues, field, registrationId, token, operation).pipe( getFirstCompletedRemoteData(), map((rd) => { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 9d3161f0aaf..c2be335f99f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7222,7 +7222,7 @@ "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", - "external-login.validate-email.no-token": "The validation link is no longer valid. Please try again.", + "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", } From 95600d75761da5ca5292647227c5a90e51b84371 Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Mon, 2 Oct 2023 15:43:37 +0300 Subject: [PATCH 21/56] [DSC-1226] set this.model.metadataValue as currentValue if present on init in dynamic-scrollable-dropdown.component.ts --- .../dynamic-scrollable-dropdown.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts index 75a21973590..4be743f2162 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts @@ -77,6 +77,10 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom * Initialize the component, setting up the init form value */ ngOnInit() { + if (this.model.metadataValue) { + this.setCurrentValue(this.model.metadataValue, true); + } + this.updatePageInfo(this.model.maxOptions, 1); this.retrieveEntries(null, true); From 5cc7a44dbac0bfeb567dfea755732d59f95c917c Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 3 Oct 2023 17:40:38 +0200 Subject: [PATCH 22/56] [CST-10703] final workflow implemntation --- src/app/core/auth/models/auth.method-type.ts | 3 +- src/app/core/core.module.ts | 2 +- .../data/eperson-registration.service.spec.ts | 4 +- .../core/data/eperson-registration.service.ts | 17 ++- ...-email-confirmation-page.component.spec.ts | 20 ++- .../external-login-page-routing.module.ts | 4 + .../external-login-page.component.html | 6 +- .../external-login-page.component.spec.ts | 64 ++++++++- .../external-login-page.component.ts | 45 +++--- ...review-account-info-page-routing.module.ts | 4 + ...in-review-account-info-page.component.html | 4 +- ...review-account-info-page.component.spec.ts | 52 ++++++- ...ogin-review-account-info-page.component.ts | 45 +++--- .../helpers/review-account.guard.spec.ts | 79 +++++++++++ .../helpers/review-account.guard.ts | 73 ++++++++++ .../review-account-info.component.html | 2 +- .../review-account-info.component.ts | 75 ++-------- .../email-validated.component.html | 2 +- .../email-validated.component.spec.ts | 3 +- .../email-validated.component.ts | 11 +- ...al-login-validation-page-routing.module.ts | 5 +- ...ernal-login-validation-page.component.html | 6 +- ...al-login-validation-page.component.spec.ts | 62 ++++----- ...xternal-login-validation-page.component.ts | 98 +++---------- ...stration-data-create-user.resolver.spec.ts | 129 ++++++++++++++++++ .../registration-data-create-user.resolver.ts | 93 +++++++++++++ .../invitation-acceptance.component.spec.ts | 2 +- .../invitation-acceptance.component.ts | 2 +- src/app/invitation/valid-token.guard.spec.ts | 4 +- src/app/invitation/valid-token.guard.ts | 2 +- src/app/login-page/login-page.component.html | 1 + src/app/login-page/login-page.component.ts | 4 + .../registration.resolver.spec.ts | 2 +- .../registration.resolver.ts | 2 +- .../register-page/registration.guard.spec.ts | 6 +- src/app/register-page/registration.guard.ts | 2 +- .../confirm-email.component.html | 2 +- .../confirm-email.component.spec.ts | 6 +- .../confirm-email/confirm-email.component.ts | 92 ++++++++++++- .../provide-email.component.spec.ts | 20 ++- .../provide-email/provide-email.component.ts | 36 +++-- .../external-log-in.component.html | 10 +- .../external-log-in.component.spec.ts | 65 ++++++--- .../external-log-in.component.ts | 10 +- .../guards/registration-token.guard.spec.ts | 79 +++++++++++ .../guards/registration-token.guard.ts | 54 ++++++++ .../models/registration-data.mock.model.ts | 5 +- .../models/registration-data.model.ts | 14 +- .../registration-data.resolver.spec.ts | 16 +++ .../resolvers/registration-data.resolver.ts | 50 +++++++ .../services/external-login.service.ts | 7 +- src/app/shared/log-in/log-in.component.html | 9 +- src/app/shared/log-in/log-in.component.ts | 18 ++- src/assets/i18n/en.json5 | 10 ++ src/assets/i18n/it.json5 | 44 ++++++ 55 files changed, 1133 insertions(+), 349 deletions(-) create mode 100644 src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts create mode 100644 src/app/external-login-review-account-info/helpers/review-account.guard.ts create mode 100644 src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts create mode 100644 src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts create mode 100644 src/app/shared/external-log-in-complete/guards/registration-token.guard.spec.ts create mode 100644 src/app/shared/external-log-in-complete/guards/registration-token.guard.ts create mode 100644 src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts create mode 100644 src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts diff --git a/src/app/core/auth/models/auth.method-type.ts b/src/app/core/auth/models/auth.method-type.ts index 600c3c80761..e5b4e0a194c 100644 --- a/src/app/core/auth/models/auth.method-type.ts +++ b/src/app/core/auth/models/auth.method-type.ts @@ -5,5 +5,6 @@ export enum AuthMethodType { Ip = 'ip', X509 = 'x509', Oidc = 'oidc', - Orcid = 'orcid' + Orcid = 'orcid', + Validation = 'validation', } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index d22aadc8a55..c75ce8e65c9 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -472,7 +472,7 @@ export const models = LoginStatistics, Metric, ItemRequest, - RegistrationData + RegistrationData, ]; @NgModule({ diff --git a/src/app/core/data/eperson-registration.service.spec.ts b/src/app/core/data/eperson-registration.service.spec.ts index 74e45591125..5fcfbcbdcd7 100644 --- a/src/app/core/data/eperson-registration.service.spec.ts +++ b/src/app/core/data/eperson-registration.service.spec.ts @@ -104,7 +104,7 @@ describe('EpersonRegistrationService', () => { describe('searchByToken', () => { it('should return a registration corresponding to the provided token', () => { - const expected = service.searchByToken('test-token'); + const expected = service.searchByTokenAndUpdateData('test-token'); expect(expected).toBeObservable(cold('(a|)', { a: jasmine.objectContaining({ @@ -124,7 +124,7 @@ describe('EpersonRegistrationService', () => { testScheduler.run(({ cold, expectObservable }) => { rdbService.buildSingle.and.returnValue(cold('a', { a: rd })); - service.searchByToken('test-token'); + service.searchByTokenAndUpdateData('test-token'); expect(requestService.send).toHaveBeenCalledWith( jasmine.objectContaining({ diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 3385c520dc5..259b5f66c50 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -89,10 +89,11 @@ export class EpersonRegistrationService{ } /** - * Search a registration based on the provided token - * @param token + * Searches for a registration based on the provided token. + * @param token The token to search for. + * @returns An observable of remote data containing the registration. */ - searchByToken(token: string): Observable> { + searchByTokenAndUpdateData(token: string): Observable> { const requestId = this.requestService.generateRequestId(); const href$ = this.getTokenSearchEndpoint(token).pipe( @@ -123,7 +124,13 @@ export class EpersonRegistrationService{ }) ); } - searchByTokenAndHandleError(token: string): Observable> { + + /** + * Searches for a registration by token and handles any errors that may occur. + * @param token The token to search for. + * @returns An observable of remote data containing the registration. + */ + searchRegistrationByToken(token: string): Observable> { const requestId = this.requestService.generateRequestId(); const href$ = this.getTokenSearchEndpoint(token).pipe( @@ -150,7 +157,7 @@ export class EpersonRegistrationService{ * @param updateValue Flag to indicate if the email should be updated or added * @returns Remote Data state of the patch request */ - patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operator: 'add' | 'replace') { + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operator: 'add' | 'replace'): Observable> { const requestId = this.requestService.generateRequestId(); const href$ = this.getRegistrationEndpoint().pipe( diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts index cd646f25cf0..89a96222d13 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; +import { ConfirmationSentComponent } from '../shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; describe('ExternalLoginEmailConfirmationPageComponent', () => { let component: ExternalLoginEmailConfirmationPageComponent; @@ -8,7 +11,17 @@ describe('ExternalLoginEmailConfirmationPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ExternalLoginEmailConfirmationPageComponent ] + declarations: [ + ExternalLoginEmailConfirmationPageComponent, + ConfirmationSentComponent ], + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + ] }) .compileComponents(); }); @@ -22,4 +35,9 @@ describe('ExternalLoginEmailConfirmationPageComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render ConfirmationSentComponent', () => { + const compiled = fixture.nativeElement; + expect(compiled.querySelector('ds-confirmation-sent')).toBeTruthy(); + }); }); diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index 7adbbb7b034..390db1eaf00 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -1,12 +1,16 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; +import { RegistrationTokenGuard } from '../shared/external-log-in-complete/guards/registration-token.guard'; +import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; const routes: Routes = [ { path: '', pathMatch: 'full', component: ThemedExternalLoginPageComponent, + // canActivate: [RegistrationTokenGuard], // TODO: uncomment this line to enable the guard later + resolve: { registrationData: RegistrationDataResolver }, }, ]; diff --git a/src/app/external-login-page/external-login-page.component.html b/src/app/external-login-page/external-login-page.component.html index 82c9419930b..755896211de 100644 --- a/src/app/external-login-page/external-login-page.component.html +++ b/src/app/external-login-page/external-login-page.component.html @@ -1,11 +1,11 @@
- +
diff --git a/src/app/external-login-page/external-login-page.component.spec.ts b/src/app/external-login-page/external-login-page.component.spec.ts index 20f46ae833c..c705b131b3e 100644 --- a/src/app/external-login-page/external-login-page.component.spec.ts +++ b/src/app/external-login-page/external-login-page.component.spec.ts @@ -1,20 +1,53 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginPageComponent } from './external-login-page.component'; -import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; -import { Router } from '@angular/router'; -import { RouterMock } from '../shared/mocks/router.mock'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; +import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; +import { CommonModule } from '@angular/common'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; describe('ExternalLoginPageComponent', () => { let component: ExternalLoginPageComponent; let fixture: ComponentFixture; + const registrationDataMock = { + registrationType: 'orcid', + email: 'test@test.com', + netId: '0000-0000-0000-0000', + user: 'a44d8c9e-9b1f-4e7f-9b1a-5c9d8a0b1f1a', + registrationMetadata: { + 'email': [{ value: 'test@test.com' }], + 'eperson.lastname': [{ value: 'Doe' }], + 'eperson.firstname': [{ value: 'John' }], + }, + }; + beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ ExternalLoginPageComponent ], providers: [ - { provide: EpersonRegistrationService, useValue: {} }, - { provide: Router, useValue: new RouterMock() }, + { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: { + token: '1234567890', + }, + }, + data: of(registrationDataMock), + }, + }, + ], + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), ], }) .compileComponents(); @@ -29,4 +62,25 @@ describe('ExternalLoginPageComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set the token from the query params', () => { + expect(component.token).toEqual('1234567890'); + }); + + it('should set the hasErrors flag if the token is not present', () => { + const activatedRoute = TestBed.inject(ActivatedRoute); + activatedRoute.snapshot.queryParams.token = undefined; + fixture.detectChanges(); + expect(component.hasErrors).toBeTrue(); + }); + + it('should display the DsExternalLogIn component when there are no errors', () => { + const registrationData = Object.assign(new RegistrationData(), registrationDataMock); + component.registrationData$ = of(registrationData); + component.token = '1234567890'; + component.hasErrors = false; + fixture.detectChanges(); + const dsExternalLogInComponent = fixture.nativeElement.querySelector('ds-external-log-in'); + expect(dsExternalLogInComponent).toBeTruthy(); + }); }); diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index b98e823fc90..c8bde7cd0da 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -1,13 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { hasValue } from '../shared/empty.util'; -import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; -import { Registration } from '../core/shared/registration.model'; +import { hasNoValue } from '../shared/empty.util'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; import { AlertType } from '../shared/alert/aletr-type'; -import { Observable, map, of } from 'rxjs'; -import { getRemoteDataPayload } from '../core/shared/operators'; +import { Observable, first, map, of, tap } from 'rxjs'; @Component({ templateUrl: './external-login-page.component.html', @@ -23,41 +20,35 @@ export class ExternalLoginPageComponent implements OnInit { /** * The registration data of the user. */ - public registrationData$: Observable = of( - mockRegistrationDataModel - ); + public registrationData$: Observable; /** * The type of alert to show. */ public AlertTypeEnum = AlertType; + /** + * Whether the component has errors. + */ + public hasErrors = false; constructor( - private epersonRegistrationService: EpersonRegistrationService, private arouter: ActivatedRoute ) { this.token = this.arouter.snapshot.queryParams.token; + this.hasErrors = hasNoValue(this.arouter.snapshot.queryParams.token); } ngOnInit(): void { - this.getRegistrationData(); + this.registrationData$ = this.arouter.data.pipe( + first(), + tap((data) => this.hasErrors = hasNoValue(data.registrationData)), + map((data) => data.registrationData)); + + // TODO: remove this line (temporary) + this.registrationData$ = of( + mockRegistrationDataModel + ); + this.hasErrors = false; this.token = '1234567890'; } - - /** - * Get the registration data of the user, - * based on the token. - */ - getRegistrationData() { - if (hasValue(this.token)) { - this.registrationData$ = this.epersonRegistrationService - .searchByToken(this.token) - .pipe( - getRemoteDataPayload(), - map((registration: Registration) => - Object.assign(new RegistrationData(), registration) - ) - ); - } - } } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts index 94f2ef42664..ffec9928371 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts @@ -1,12 +1,16 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; +import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; + const routes: Routes = [ { path: '', pathMatch: 'full', component: ExternalLoginReviewAccountInfoPageComponent, + // canActivate: [ReviewAccountGuard],// TODO: Remove comment + resolve: { registrationData: RegistrationDataResolver } },]; @NgModule({ diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html index 6d5defa2ca4..97034110392 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html @@ -1,12 +1,12 @@
- + diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts index ef2dfe82017..7622f081d0d 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts @@ -1,16 +1,33 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; +import { AlertType } from '../shared/alert/aletr-type'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; +import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; describe('ExternalLoginReviewAccountInfoPageComponent', () => { let component: ExternalLoginReviewAccountInfoPageComponent; let fixture: ComponentFixture; + const mockActivatedRoute = { + snapshot: { + queryParams: { + token: '1234567890' + } + }, + data: of({ + registrationData: mockRegistrationDataModel + }) + }; + beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ExternalLoginReviewAccountInfoPageComponent ] + declarations: [ExternalLoginReviewAccountInfoPageComponent], + providers: [ + { provide: ActivatedRoute, useValue: mockActivatedRoute } + ] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { @@ -22,4 +39,33 @@ describe('ExternalLoginReviewAccountInfoPageComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set the token from the query params', () => { + expect(component.token).toEqual('1234567890'); + }); + + it('should set hasErrors to false if registrationData is not empty', () => { + expect(component.hasErrors).toBeFalse(); + }); + + it('should set the registrationData$', () => { + component.registrationData$.subscribe((registrationData) => { + expect(registrationData.email).toEqual(mockRegistrationDataModel.email); + }); + }); + + it('should display review account info component when there are no errors', () => { + component.hasErrors = false; + component.registrationData$ = of(mockRegistrationDataModel); + fixture.detectChanges(); + const reviewAccountInfoComponent = fixture.nativeElement.querySelector('ds-review-account-info'); + expect(reviewAccountInfoComponent).toBeTruthy(); + }); + + it('should display error alert when there are errors', () => { + component.hasErrors = true; + fixture.detectChanges(); + const errorAlertComponent = fixture.nativeElement.querySelector('ds-alert'); + expect(errorAlertComponent).toBeTruthy(); + }); }); diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts index 8b02a347c51..5c984445031 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -1,12 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { AlertType } from '../shared/alert/aletr-type'; -import { Observable, map } from 'rxjs'; +import { Observable, first, map, of, tap } from 'rxjs'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; -import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { ActivatedRoute } from '@angular/router'; -import { hasValue } from '../shared/empty.util'; -import { getRemoteDataPayload } from '../core/shared/operators'; -import { Registration } from '../core/shared/registration.model'; +import { hasNoValue } from '../shared/empty.util'; +import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; @Component({ templateUrl: './external-login-review-account-info-page.component.html', @@ -27,12 +25,12 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { * The registration data of the user */ public registrationData$: Observable; - // = of( - // mockRegistrationDataModel - // ); + /** + * Whether the component has errors + */ + public hasErrors = false; constructor( - private epersonRegistrationService: EpersonRegistrationService, private arouter: ActivatedRoute ) { this.token = this.arouter.snapshot.queryParams.token; @@ -40,26 +38,15 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { ngOnInit(): void { - // -> if email address is not used by other user => Email Validated component - // -> if email address is used by other user => Review account information component - this.getRegistrationData(); + this.registrationData$ = this.arouter.data.pipe(first(), + tap((data) => this.hasErrors = hasNoValue(data?.registrationData)), + map((data) => data.registrationData)); + // TODO: remove this line (temporary) - // this.token = '1234567890'; + this.registrationData$ = of( + mockRegistrationDataModel + ); + this.hasErrors = false; + this.token = '1234567890'; } - /** - * Get the registration data from the token - */ - getRegistrationData() { - if (hasValue(this.token)) { - this.registrationData$ = this.epersonRegistrationService - .searchByToken(this.token) - .pipe( - getRemoteDataPayload(), - map((registration: Registration) => - Object.assign(new RegistrationData(), registration) - ) - ); - } - } - } diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts new file mode 100644 index 00000000000..61cf3f178cc --- /dev/null +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts @@ -0,0 +1,79 @@ +import { TestBed } from '@angular/core/testing'; +import { ReviewAccountGuard } from './review-account.guard'; +import { ActivatedRoute, convertToParamMap, Params, Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; +import { EPerson } from 'src/app/core/eperson/models/eperson.model'; +import { Registration } from 'src/app/core/shared/registration.model'; +import { RouterMock } from 'src/app/shared/mocks/router.mock'; +import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; + +describe('ReviewAccountGuard', () => { + let guard: ReviewAccountGuard; + const route = new RouterMock(); + const registrationWithGroups = Object.assign(new Registration(), + { + email: 'test@email.org', + token: 'test-token', + }); + const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { + searchRegistrationByToken: createSuccessfulRemoteDataObject$(registrationWithGroups) + }); + const authService = { + getAuthenticatedUserFromStore: () => observableOf(ePerson), + setRedirectUrl: () => { + return true; + } + } as any; + const ePerson = Object.assign(new EPerson(), { + id: 'test-eperson', + uuid: 'test-eperson' + }); + beforeEach(() => { + const paramObject: Params = {}; + paramObject.token = '1234'; + TestBed.configureTestingModule({ + providers: [{provide: Router, useValue: route}, + { + provide: ActivatedRoute, + useValue: { + queryParamMap: observableOf(convertToParamMap(paramObject)) + }, + }, + {provide: EpersonRegistrationService, useValue: epersonRegistrationService}, + {provide: AuthService, useValue: authService} + ] + }); + guard = TestBed.get(ReviewAccountGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); + describe('based on the response of "searchByToken have', () => { + it('can activate must return true when registration data includes groups', () => { + (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) + .subscribe( + (canActivate) => { + expect(canActivate).toEqual(true); + } + ); + }); + it('can activate must return false when registration data includes groups', () => { + const registrationWithDifferentUsedFromLoggedInt = Object.assign(new Registration(), + { + email: 't1@email.org', + token: 'test-token', + }); + epersonRegistrationService.searchRegistrationByToken.and.returnValue(observableOf(registrationWithDifferentUsedFromLoggedInt)); + (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) + .subscribe( + (canActivate) => { + expect(canActivate).toEqual(false); + } + ); + }); + + }); +}); diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.ts new file mode 100644 index 00000000000..c979e3bfc2c --- /dev/null +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + Router, + RouterStateSnapshot, +} from '@angular/router'; +import isEqual from 'lodash/isEqual'; +import { Observable, catchError, mergeMap, of, tap } from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; +import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; +import { Registration } from 'src/app/core/shared/registration.model'; +import { hasValue } from 'src/app/shared/empty.util'; +import { RegistrationData } from 'src/app/shared/external-log-in-complete/models/registration-data.model'; + +@Injectable({ + providedIn: 'root', +}) +export class ReviewAccountGuard implements CanActivate { + constructor( + private router: Router, + private epersonRegistrationService: EpersonRegistrationService, + private authService: AuthService + ) { } + + /** + * Determines if a user can activate a route based on the registration token. + * @param route - The activated route snapshot. + * @param state - The router state snapshot. + * @returns A value indicating if the user can activate the route. + */ + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Promise | boolean | Observable { + if (route.params.token) { + return this.epersonRegistrationService + .searchRegistrationByToken(route.params.token) + .pipe( + getFirstCompletedRemoteData(), + mergeMap( + (data: RemoteData) => { + if (data.hasSucceeded && hasValue(data)) { + const registrationData = Object.assign(new RegistrationData(), data.payload); + // is the registration type validation (account valid) + if (isEqual(registrationData.registrationType, AuthMethodType.Validation)) { + return of(true); + } else { + return this.authService.isAuthenticated(); + } + } + return of(false); + } + ), + tap((isValid: boolean) => { + if (!isValid) { + this.router.navigate(['/404']); + } + }), + catchError(() => { + this.router.navigate(['/404']); + return of(false); + }) + ); + } else { + this.router.navigate(['/404']); + return of(false); + } + } +} diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html index 82aec84441e..039bc763b28 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html @@ -41,7 +41,7 @@

{{'external-login-validation.review-account-info.header' | translate}}

{ - if (eperson) { - this.epersonCurrentData = eperson; - this.dataToCompare = this.prepareDataToCompare(); - } - }); - } + this.dataToCompare = this.prepareDataToCompare(); } /** @@ -157,7 +117,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { filter((data: ReviewAccountInfoData) => data.overrideValue), switchMap((data: ReviewAccountInfoData) => { return this.ePersonService.mergeEPersonDataWithToken( - this.epersonCurrentData.id, + this.registrationData.user, this.registrationToken, data.identifier ); @@ -165,15 +125,14 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } else { override$ = this.ePersonService.mergeEPersonDataWithToken( - this.epersonCurrentData.id, + this.registrationData.user, this.registrationToken ); } this.subs.push( override$.subscribe((response: RemoteData) => { - // TODO: https://4science.atlassian.net/browse/CST-11609?focusedCommentId=206748 - // redirect to profile page if (response.hasSucceeded) { + // TODO: remove this line (temporary) console.log('mergeEPersonDataWithToken', response.payload); this.notificationService.success( this.translateService.get( @@ -197,7 +156,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { /** * Prepare the data to compare and display: * -> For each metadata from the registration token, get the current value from the eperson. - * -> Label is the metadata key without the prefix e.g `eperson.` + * -> Label is the metadata key without the prefix e.g `eperson.` but only `email` * -> Identifier is the metadata key with the prefix e.g `eperson.lastname` * -> Override value is false by default * @returns List of data to compare @@ -209,7 +168,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { console.log(key, value); dataToCompare.push({ label: key.split('.')?.[1] ?? key.split('.')?.[0], - currentValue: this.getCurrentValue(key), + currentValue: value[0]?.overrides ?? '', receivedValue: value[0].value, overrideValue: false, identifier: key, @@ -220,18 +179,6 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { return dataToCompare; } - /** - * Return the current value of the metadata key from the eperson - * @param metadata metadata key - * @returns the current value of the metadata key from the eperson - */ - private getCurrentValue(metadata: string): string { - if (metadata === 'email') { - return this.epersonCurrentData.email; - } - return this.epersonCurrentData.firstMetadataValue(metadata) ?? ''; - } - ngOnDestroy(): void { this.subs.filter((s) => hasValue(s)).forEach((sub) => sub.unsubscribe()); } diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.html b/src/app/external-login-validation-page/email-validated/email-validated.component.html index 133243853bf..b58ed085890 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.html +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.html @@ -5,5 +5,5 @@

- +
diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts index fee341ad22c..21c7f5d7d8f 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts @@ -9,6 +9,7 @@ import { Router } from '@angular/router'; import { RouterStub } from 'src/app/shared/testing/router.stub'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { of } from 'rxjs'; +import { AuthServiceMock } from 'src/app/shared/mocks/auth.service.mock'; describe('EmailValidatedComponent', () => { let component: EmailValidatedComponent; @@ -27,7 +28,7 @@ describe('EmailValidatedComponent', () => { await TestBed.configureTestingModule({ declarations: [ EmailValidatedComponent ], providers: [ - { provide: AuthService, useValue: {}}, + { provide: AuthService, useValue: new AuthServiceMock()}, { provide: Router, useValue: new RouterStub() }, { provide: TranslateService, useValue: translateServiceStub }, ], diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts index 4147dd23720..8fd45bc4a7f 100644 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.ts +++ b/src/app/external-login-validation-page/email-validated/email-validated.component.ts @@ -1,5 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; import { AuthService } from '../../core/auth/auth.service'; @Component({ selector: 'ds-email-validated', @@ -9,11 +8,9 @@ import { AuthService } from '../../core/auth/auth.service'; }) export class EmailValidatedComponent { - // TODO: (temporary) - // evaluate if this is needed - @Input() registrationToken: string; - - constructor(private authService: AuthService, private router: Router) { + constructor(private authService: AuthService) { + // After the user has validated his email, we need to redirect him to the review account page, + // in order to review his account information this.authService.setRedirectUrl('/review-account'); } } diff --git a/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts b/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts index d206eba0fe8..241cebebb2f 100644 --- a/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts +++ b/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts @@ -1,12 +1,15 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; - +import { RegistrationTokenGuard } from '../shared/external-log-in-complete/guards/registration-token.guard'; +import { RegistrationDataCreateUserResolver } from './resolvers/registration-data-create-user.resolver'; const routes: Routes = [ { path: '', pathMatch: 'full', component: ThemedExternalLoginValidationPageComponent, + // canActivate: [RegistrationTokenGuard] // TODO: uncomment this line to enable the guard later + resolve: { createUser: RegistrationDataCreateUserResolver }, }, ]; diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html index 3efd1ce0e38..cfdaed0d6db 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.html +++ b/src/app/external-login-validation-page/external-login-validation-page.component.html @@ -1,9 +1,9 @@
- - + + diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts index 155d64fe082..975fca9f81f 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; -import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { Observable, of } from 'rxjs'; import { RemoteData } from '../core/data/remote-data'; @@ -10,11 +9,12 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; import { ActivatedRoute } from '@angular/router'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { AlertType } from '../shared/alert/aletr-type'; +import { tr } from 'date-fns/locale'; describe('ExternalLoginValidationPageComponent', () => { let component: ExternalLoginValidationPageComponent; + let componentAsAny: any; let fixture: ComponentFixture; let epersonRegistrationService: any; @@ -37,19 +37,21 @@ describe('ExternalLoginValidationPageComponent', () => { queryParams: { token: tokenMock } - } + }, + data: of({ + createUser: true, + }), }; beforeEach(async () => { epersonRegistrationService = { - searchByToken: (token: string): Observable> => { return createSuccessfulRemoteDataObject$(registrationDataMock); } + searchByTokenAndUpdateData: (token: string): Observable> => { return createSuccessfulRemoteDataObject$(registrationDataMock); } }; await TestBed.configureTestingModule({ declarations: [ExternalLoginValidationPageComponent], providers: [ { provide: ActivatedRoute, useValue: routeStub }, - { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, ], imports: [ CommonModule, @@ -68,6 +70,7 @@ describe('ExternalLoginValidationPageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ExternalLoginValidationPageComponent); component = fixture.componentInstance; + componentAsAny = component; fixture.detectChanges(); }); @@ -75,49 +78,32 @@ describe('ExternalLoginValidationPageComponent', () => { expect(component).toBeTruthy(); }); - it('should set the token from the query parameters', () => { - expect(component.token).toEqual(tokenMock); - }); - - it('should initialize the registration data', () => { - spyOn(epersonRegistrationService, 'searchByToken').and.callThrough(); + it('should set validationFailed to true if createUser is falsy', () => { + const activatedRoute = TestBed.inject(ActivatedRoute); + activatedRoute.data = of({ createUser: false }); component.ngOnInit(); - expect(epersonRegistrationService.searchByToken).toHaveBeenCalledWith(tokenMock); - expect(component.registrationData$).toBeTruthy(); + expect(componentAsAny.validationFailed.value).toBeTrue(); }); - it('should render ds-email-validated component when registrationData$ does not have an email', () => { - component.registrationData$ = of(Object.assign(new RegistrationData(), { registrationDataMock, email: null })); + it('should set validationFailed to false if createUser is truthy', () => { + const activatedRoute = TestBed.inject(ActivatedRoute); + activatedRoute.data = of({ createUser: true }); component.ngOnInit(); - fixture.detectChanges(); - - const emailValidatedComponent = fixture.nativeElement.querySelector('ds-email-validated'); - const reviewAccountInfoComponent = fixture.nativeElement.querySelector('ds-review-account-info'); - - expect(emailValidatedComponent).toBeTruthy(); - expect(reviewAccountInfoComponent).toBeNull(); + expect(componentAsAny.validationFailed.value).toBeFalse(); }); - it('should render ds-review-account-info component when registrationData$ has an email', () => { - component.registrationData$ = of(Object.assign(new RegistrationData(), { registrationDataMock, email: 'hey@hello.com' })); - // component.ngOnInit(); + it('should show DsEmailValidatedComponent when hasFailed() returns false', () => { + spyOn(component, 'hasFailed').and.returnValue(of(false)); fixture.detectChanges(); - const emailValidatedComponent = fixture.nativeElement.querySelector('ds-email-validated'); - const reviewAccountInfoComponent = fixture.nativeElement.querySelector('ds-review-account-info'); - - expect(emailValidatedComponent).toBeNull(); - expect(reviewAccountInfoComponent).toBeTruthy(); + const dsEmailValidated = fixture.nativeElement.querySelector('ds-email-validated'); + expect(dsEmailValidated).toBeTruthy(); }); - it('should render ds-alert component when token is missing', () => { - component.token = null; - component.ngOnInit(); + it('should show DsAlertComponent when hasFailed() returns true', () => { + spyOn(component, 'hasFailed').and.returnValue(of(true)); fixture.detectChanges(); - - const alertComponent = fixture.nativeElement.querySelector('ds-alert'); - - expect(alertComponent).toBeTruthy(); - expect(component.AlertTypeEnum).toEqual(AlertType); + const dsAlert = fixture.nativeElement.querySelector('ds-alert'); + expect(dsAlert).toBeTruthy(); }); afterEach(() => { diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts index 7a20473ddbe..abcd256f8a7 100644 --- a/src/app/external-login-validation-page/external-login-validation-page.component.ts +++ b/src/app/external-login-validation-page/external-login-validation-page.component.ts @@ -1,110 +1,44 @@ -import { Component } from '@angular/core'; -import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { AlertType } from '../shared/alert/aletr-type'; -import { hasNoValue, hasValue } from '../shared/empty.util'; -import { getRemoteDataPayload } from '../core/shared/operators'; -import { BehaviorSubject, Observable, map, of, switchMap, tap } from 'rxjs'; -import { Registration } from '../core/shared/registration.model'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; -import { RemoteData } from '../core/data/remote-data'; -import { NotificationsService } from '../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { EPersonDataService } from '../core/eperson/eperson-data.service'; -import { MetadataValue } from '../core/shared/metadata.models'; -import { EPerson } from '../core/eperson/models/eperson.model'; -import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; +import { BehaviorSubject, Observable } from 'rxjs'; @Component({ templateUrl: './external-login-validation-page.component.html', styleUrls: ['./external-login-validation-page.component.scss'], }) -export class ExternalLoginValidationPageComponent { - /** - * The token used to get the registration data - */ - public token: string; - +export class ExternalLoginValidationPageComponent implements OnInit { /** * The type of alert to show */ public AlertTypeEnum = AlertType; + /** + * Whether the component has errors + */ private validationFailed: BehaviorSubject = new BehaviorSubject(false); constructor( - private epersonRegistrationService: EpersonRegistrationService, private arouter: ActivatedRoute, - private epersonDataService: EPersonDataService, - private notificationService: NotificationsService, - private translateService: TranslateService ) { - this.token = this.arouter.snapshot.queryParams.token; - this.token = '1234567890'; // TODO: remove this line (temporary) } ngOnInit(): void { - // TODO: Uncomment this line later - // this.getRegistrationData(); - } + this.arouter.data.subscribe((data) => { + const resolvedData = data.createUser; + this.validationFailed.next(!resolvedData); + }); - public hasFailed(): Observable { - return this.validationFailed.asObservable(); + // TODO: remove this line (temporary) + this.validationFailed.next(false); } + /** - * Get the registration data from the token + * Check if the validation has failed */ - getRegistrationData() { - this.validationFailed.next(true); - - if (hasValue(this.token)) { - this.fetchRegistrationDataAndCreateUser(this.token); - } - } - - fetchRegistrationDataAndCreateUser(token: string) { - this.epersonRegistrationService - .searchByToken(token) - .pipe( - switchMap((rd) => { - if (hasValue(rd.payload) && hasNoValue(rd.payload.user)) { - const registrationData = Object.assign( - new RegistrationData(), - rd.payload - ); - return this.createUserFromToken(token, registrationData); - } else { - return of(rd); - } - }) - ) - .subscribe((rd: RemoteData) => { - if (rd.hasFailed) { - this.validationFailed.next(true); - } - }); - } - - createUserFromToken(token: string, registrationData: RegistrationData) { - const metadataValues = Object.entries(registrationData.registrationMetadata) - .filter(([key, value]) => hasValue(value[0]?.value)) - .map(([key, value]) => ({ - key, - value: value[0]?.value, - })); - const eperson = Object.assign(new EPerson(), { - metadata: metadataValues, - canLogIn: true, - requireCertificate: false, - }); - return this.epersonDataService.createEPersonForToken(eperson, token).pipe( - tap((rd: RemoteData) => { - if (rd.hasFailed) { - this.validationFailed.next(true); - } - }) - ); + public hasFailed(): Observable { + return this.validationFailed.asObservable(); } } diff --git a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts new file mode 100644 index 00000000000..979bdc89491 --- /dev/null +++ b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts @@ -0,0 +1,129 @@ +import { TestBed } from '@angular/core/testing'; + +import { RegistrationDataCreateUserResolver } from './registration-data-create-user.resolver'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RouterStateSnapshot } from '@angular/router'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; +import { Registration } from '../../core/shared/registration.model'; +import { MetadataValue } from 'src/app/core/shared/metadata.models'; + +describe('RegistrationDataCreateUserResolver', () => { + let resolver: RegistrationDataCreateUserResolver; + let epersonRegistrationService: jasmine.SpyObj; + let epersonDataService: jasmine.SpyObj; + + const registrationDataMock = { + registrationType: 'orcid', + email: 'test@test.com', + netId: '0000-0000-0000-0000', + user: null, + registrationMetadata: { + 'email': [{ value: 'test@test.com' }], + 'eperson.lastname': [{ value: 'Doe' }], + 'eperson.firstname': [{ value: 'John' }], + }, + }; + + const tokenMock = 'as552-5a5a5-5a5a5-5a5a5'; + + + beforeEach(() => { + const spyEpersonRegistrationService = jasmine.createSpyObj('EpersonRegistrationService', [ + 'searchByTokenAndUpdateData' + ]); + const spyEpersonDataService = jasmine.createSpyObj('EPersonDataService', [ + 'createEPersonForToken' + ]); + + TestBed.configureTestingModule({ + providers: [ + RegistrationDataCreateUserResolver, + { provide: EpersonRegistrationService, useValue: spyEpersonRegistrationService }, + { provide: EPersonDataService, useValue: spyEpersonDataService } + ] + }); + resolver = TestBed.inject(RegistrationDataCreateUserResolver); + epersonRegistrationService = TestBed.inject(EpersonRegistrationService) as jasmine.SpyObj; + epersonDataService = TestBed.inject(EPersonDataService) as jasmine.SpyObj; + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); + + describe('resolve', () => { + const token = tokenMock; + const route = { params: { token: token } } as any; + const state = {} as RouterStateSnapshot; + + it('should create a new user and return true when registration data does not contain a user', (done) => { + const registration = new Registration(); + epersonRegistrationService.searchByTokenAndUpdateData.and.returnValue(createSuccessfulRemoteDataObject$(registration)); + + const createdEPerson = new EPerson(); + createdEPerson.email = registrationDataMock.email; + createdEPerson.metadata = { + 'eperson.lastname': [Object.assign(new MetadataValue(), { value: 'Doe' })], + 'eperson.firstname': [Object.assign(new MetadataValue(), { value: 'John' })], + }; + createdEPerson.canLogIn = true; + createdEPerson.requireCertificate = false; + epersonDataService.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(createdEPerson)); + + resolver.resolve(route, state).subscribe((result) => { + expect(result).toBeTrue(); + expect(epersonRegistrationService.searchByTokenAndUpdateData).toHaveBeenCalledWith(token); + expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(createdEPerson, token); + done(); + }); + }); + + it('should return false when search by token and update data fails', (done) => { + epersonRegistrationService.searchByTokenAndUpdateData.and.returnValue(createFailedRemoteDataObject$()); + + resolver.resolve(route, state).subscribe((result) => { + expect(result).toBeFalse(); + expect(epersonRegistrationService.searchByTokenAndUpdateData).toHaveBeenCalledWith(token); + expect(epersonDataService.createEPersonForToken).not.toHaveBeenCalled(); + done(); + }); + }); + + }); + + describe('createUserFromToken', () => { + const token = tokenMock; + const registrationData: RegistrationData = Object.assign(new RegistrationData(), registrationDataMock); + + it('should create a new user and return true', (done) => { + const createdEPerson = new EPerson(); + createdEPerson.email = registrationData.email; + createdEPerson.metadata = { + 'eperson.lastname': 'Doe', //[{ value: 'Doe' }], + 'eperson.firstname': 'John',// [{ value: 'John' }], + } as any; + createdEPerson.canLogIn = true; + createdEPerson.requireCertificate = false; + epersonDataService.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(createdEPerson)); + + resolver.createUserFromToken(token, registrationData).subscribe((result) => { + expect(result).toBeTrue(); + expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(createdEPerson, token); + done(); + }); + }); + + it('should return false when create EPerson for token fails', (done) => { + epersonDataService.createEPersonForToken.and.returnValue(createFailedRemoteDataObject$()); + + resolver.createUserFromToken(token, registrationData).subscribe((result) => { + expect(result).toBeFalse(); + expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(jasmine.any(EPerson), token); + done(); + }); + }); + }); +}); diff --git a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts new file mode 100644 index 00000000000..000422f7623 --- /dev/null +++ b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@angular/core'; +import { + Resolve, + RouterStateSnapshot, + ActivatedRouteSnapshot, +} from '@angular/router'; +import { Observable, map, of, switchMap } from 'rxjs'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { hasValue, hasNoValue } from '../../shared/empty.util'; +import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; + +@Injectable({ + providedIn: 'root', +}) +export class RegistrationDataCreateUserResolver implements Resolve { + constructor( + private epersonRegistrationService: EpersonRegistrationService, + private epersonDataService: EPersonDataService + ) {} + + /** + * Resolves the registration data and creates a new user account from the given token. + * The account will be created only when the registration data does NOT contain a user (uuid). + * @param route The activated route snapshot. + * @param state The router state snapshot. + * @returns An observable of a boolean indicating whether the user was created successfully or not. + */ + resolve( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable { + const token = route.params.token; + return this.epersonRegistrationService + .searchByTokenAndUpdateData(token) + .pipe( + getFirstCompletedRemoteData(), + switchMap((rd) => { + if ( + rd.hasSucceeded && + hasValue(rd.payload) && + hasNoValue(rd.payload.user) + ) { + const registrationData = Object.assign( + new RegistrationData(), + rd.payload + ); + return this.createUserFromToken(token, registrationData); + } + if (rd.hasFailed) { + return of(false); + } + }) + ); + } + + /** + * Creates a new user from a given token and registration data. + * Based on the registration data, the user will be created with the following properties: + * - email: the email address from the registration data + * - metadata: all metadata values from the registration data, except for the email metadata key (ePerson object does not have an email metadata field) + * - canLogIn: true + * - requireCertificate: false + * @param token The token used to create the user. + * @param registrationData The registration data used to create the user. + * @returns An Observable that emits a boolean indicating whether the user creation was successful. + */ + createUserFromToken( + token: string, + registrationData: RegistrationData + ): Observable { + const metadataValues = {}; + for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { + if (hasValue(value[0]?.value) && key !== 'email') { + metadataValues[key] = value[0]?.value; + } + } + const eperson = new EPerson(); + eperson.email = registrationData.email; + eperson.metadata = metadataValues; + eperson.canLogIn = true; + eperson.requireCertificate = false; + return this.epersonDataService.createEPersonForToken(eperson, token).pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + return rd.hasSucceeded; + }) + ); + } +} diff --git a/src/app/invitation/invitation-acceptance/invitation-acceptance.component.spec.ts b/src/app/invitation/invitation-acceptance/invitation-acceptance.component.spec.ts index 31d23e2b073..9d9dcf79c3c 100644 --- a/src/app/invitation/invitation-acceptance/invitation-acceptance.component.spec.ts +++ b/src/app/invitation/invitation-acceptance/invitation-acceptance.component.spec.ts @@ -27,7 +27,7 @@ describe('InvitationAcceptanceComponent', () => { groupNames: ['group1', 'group2'] }); const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchByToken: createSuccessfulRemoteDataObject$(registrationWithGroups) + searchByTokenAndUpdateData: createSuccessfulRemoteDataObject$(registrationWithGroups) }); const ePersonDataServiceStub = { acceptInvitationToJoinGroups(person: EPerson): Observable> { diff --git a/src/app/invitation/invitation-acceptance/invitation-acceptance.component.ts b/src/app/invitation/invitation-acceptance/invitation-acceptance.component.ts index a019eb855a6..9ba247975ee 100644 --- a/src/app/invitation/invitation-acceptance/invitation-acceptance.component.ts +++ b/src/app/invitation/invitation-acceptance/invitation-acceptance.component.ts @@ -28,7 +28,7 @@ export class InvitationAcceptanceComponent implements OnInit { this.route.paramMap.pipe( switchMap((paramMap: ParamMap) => { const token = paramMap.get('registrationToken'); - return this.epersonRegistrationService.searchByToken(token); + return this.epersonRegistrationService.searchByTokenAndUpdateData(token); }), getFirstCompletedRemoteData(), getRemoteDataPayload() diff --git a/src/app/invitation/valid-token.guard.spec.ts b/src/app/invitation/valid-token.guard.spec.ts index 964942d081f..981f4506e7c 100644 --- a/src/app/invitation/valid-token.guard.spec.ts +++ b/src/app/invitation/valid-token.guard.spec.ts @@ -20,7 +20,7 @@ describe('DirectAccessGuard', () => { groupNames: ['group1', 'group2'] }); const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchByTokenAndHandleError: createSuccessfulRemoteDataObject$(registrationWithGroups) + searchRegistrationByToken: createSuccessfulRemoteDataObject$(registrationWithGroups) }); const authService = { getAuthenticatedUserFromStore: () => observableOf(ePerson), @@ -70,7 +70,7 @@ describe('DirectAccessGuard', () => { groups: [], groupNames: [] }); - epersonRegistrationService.searchByTokenAndHandleError.and.returnValue(observableOf(registrationWithDifferentUsedFromLoggedInt)); + epersonRegistrationService.searchRegistrationByToken.and.returnValue(observableOf(registrationWithDifferentUsedFromLoggedInt)); (guard.canActivate({ params: { registrationToken: '123456789' } } as any, {} as any) as any) .subscribe( (canActivate) => { diff --git a/src/app/invitation/valid-token.guard.ts b/src/app/invitation/valid-token.guard.ts index 9c26f3d85a8..0a7fe6f5987 100644 --- a/src/app/invitation/valid-token.guard.ts +++ b/src/app/invitation/valid-token.guard.ts @@ -22,7 +22,7 @@ export class ValidTokenGuard implements CanActivate { // get the url if (route.params.registrationToken) { // search by token found - return this.epersonRegistrationService.searchByTokenAndHandleError(route.params.registrationToken).pipe( + return this.epersonRegistrationService.searchRegistrationByToken(route.params.registrationToken).pipe( getFirstCompletedRemoteData(), map((data: RemoteData) => data.hasSucceeded && hasValue('groupNames') && data.payload.groupNames.length > 0), tap((isValid: boolean) => { diff --git a/src/app/login-page/login-page.component.html b/src/app/login-page/login-page.component.html index 2a95e0ce1c5..067da65739a 100644 --- a/src/app/login-page/login-page.component.html +++ b/src/app/login-page/login-page.component.html @@ -4,6 +4,7 @@

{{"login.form.header" | translate}}

diff --git a/src/app/login-page/login-page.component.ts b/src/app/login-page/login-page.component.ts index d9ecf3e8e6b..62a72ab7907 100644 --- a/src/app/login-page/login-page.component.ts +++ b/src/app/login-page/login-page.component.ts @@ -15,6 +15,7 @@ import { import { hasValue, isNotEmpty } from '../shared/empty.util'; import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; import { isAuthenticated } from '../core/auth/selectors'; +import { AuthMethodType } from '../core/auth/models/auth.method-type'; /** * This component represents the login page @@ -33,6 +34,8 @@ export class LoginPageComponent implements OnDestroy, OnInit { */ sub: Subscription; + externalLoginMethod: AuthMethodType; + /** * Initialize instance variables * @@ -48,6 +51,7 @@ export class LoginPageComponent implements OnDestroy, OnInit { ngOnInit() { const queryParamsObs = this.route.queryParams; const authenticated = this.store.select(isAuthenticated); + this.externalLoginMethod = this.route.snapshot.queryParams.authMethod; this.sub = observableCombineLatest(queryParamsObs, authenticated).pipe( filter(([params, auth]) => isNotEmpty(params.token) || isNotEmpty(params.expired)), take(1) diff --git a/src/app/register-email-form/registration.resolver.spec.ts b/src/app/register-email-form/registration.resolver.spec.ts index 86b200018d1..066740c126e 100644 --- a/src/app/register-email-form/registration.resolver.spec.ts +++ b/src/app/register-email-form/registration.resolver.spec.ts @@ -13,7 +13,7 @@ describe('RegistrationResolver', () => { beforeEach(() => { epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchByToken: createSuccessfulRemoteDataObject$(registration) + searchByTokenAndUpdateData: createSuccessfulRemoteDataObject$(registration) }); resolver = new RegistrationResolver(epersonRegistrationService); }); diff --git a/src/app/register-email-form/registration.resolver.ts b/src/app/register-email-form/registration.resolver.ts index 498d029492b..0d7c3fed13a 100644 --- a/src/app/register-email-form/registration.resolver.ts +++ b/src/app/register-email-form/registration.resolver.ts @@ -17,7 +17,7 @@ export class RegistrationResolver implements Resolve> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { const token = route.params.token; - return this.epersonRegistrationService.searchByToken(token).pipe( + return this.epersonRegistrationService.searchByTokenAndUpdateData(token).pipe( getFirstCompletedRemoteData(), ); } diff --git a/src/app/register-page/registration.guard.spec.ts b/src/app/register-page/registration.guard.spec.ts index 9fb8dd3a338..09a6f2432e1 100644 --- a/src/app/register-page/registration.guard.spec.ts +++ b/src/app/register-page/registration.guard.spec.ts @@ -48,7 +48,7 @@ describe('RegistrationGuard', () => { }); epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchByToken: observableOf(registrationRD), + searchByTokenAndUpdateData: observableOf(registrationRD), }); router = jasmine.createSpyObj('router', { navigateByUrl: Promise.resolve(), @@ -66,7 +66,7 @@ describe('RegistrationGuard', () => { describe('canActivate', () => { describe('when searchByToken returns a successful response', () => { beforeEach(() => { - (epersonRegistrationService.searchByToken as jasmine.Spy).and.returnValue(observableOf(registrationRD)); + (epersonRegistrationService.searchByTokenAndUpdateData as jasmine.Spy).and.returnValue(observableOf(registrationRD)); }); it('should return true', (done) => { @@ -93,7 +93,7 @@ describe('RegistrationGuard', () => { describe('when searchByToken returns a 404 response', () => { beforeEach(() => { - (epersonRegistrationService.searchByToken as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not Found', 404)); + (epersonRegistrationService.searchByTokenAndUpdateData as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Not Found', 404)); }); it('should redirect', () => { diff --git a/src/app/register-page/registration.guard.ts b/src/app/register-page/registration.guard.ts index a0f07d2fbe3..9f156df4737 100644 --- a/src/app/register-page/registration.guard.ts +++ b/src/app/register-page/registration.guard.ts @@ -30,7 +30,7 @@ export class RegistrationGuard implements CanActivate { */ canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { const token = route.params.token; - return this.epersonRegistrationService.searchByToken(token).pipe( + return this.epersonRegistrationService.searchByTokenAndUpdateData(token).pipe( getFirstCompletedRemoteData(), redirectOn4xx(this.router, this.authService), map((rd) => { diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html index 2ff2000d41a..455aaf75e73 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html @@ -8,7 +8,7 @@

type="email" id="email" formControlName="email" - placeholder="orcid.profile.email@example.com" + placeholder="profile.email@example.com" class="form-control form-control-lg position-relative" />
{ let component: ConfirmEmailComponent; @@ -18,6 +19,7 @@ describe('ConfirmEmailComponent', () => { providers: [ FormBuilder, { provide: EpersonRegistrationService, useValue: {} }, + { provide: ExternalLoginService, useValue: {} }, ], imports: [ CommonModule, @@ -28,7 +30,7 @@ describe('ConfirmEmailComponent', () => { } }), ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 244521d2a65..d4e470f9e72 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,7 +1,18 @@ -import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; +import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; -import { getRemoteDataPayload } from '../../../../core/shared/operators'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../core/shared/operators'; +import { RegistrationData } from '../../models/registration-data.model'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { hasValue } from '../../../../shared/empty.util'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import isEqual from 'lodash/isEqual'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { Router } from '@angular/router'; +import { Subscription } from 'rxjs'; @Component({ selector: 'ds-confirm-email', @@ -9,17 +20,24 @@ import { getRemoteDataPayload } from '../../../../core/shared/operators'; styleUrls: ['./confirm-email.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ConfirmEmailComponent { +export class ConfirmEmailComponent implements OnDestroy { emailForm: FormGroup; - @Input() registrationId: string; + @Input() registrationData: RegistrationData; @Input() token: string; + subs: Subscription[] = []; + constructor( private formBuilder: FormBuilder, private externalLoginService: ExternalLoginService, + private epersonDataService: EPersonDataService, + private notificationService: NotificationsService, + private translate: TranslateService, + private authService: AuthService, + private router: Router ) { this.emailForm = this.formBuilder.group({ email: ['', [Validators.required, Validators.email]] @@ -28,13 +46,73 @@ export class ConfirmEmailComponent { submitForm() { this.emailForm.markAllAsTouched(); + this.router.navigate(['/login'], { queryParams: { authMethod: this.registrationData.registrationType } }); + if (this.emailForm.valid) { - const email = this.emailForm.get('email').value; - this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'replace') + const confirmedEmail = this.emailForm.get('email').value; + if (confirmedEmail && isEqual(this.registrationData.email, confirmedEmail.trim())) { + this.postCreateAccountFromToken(this.token, this.registrationData); + } else { + this.patchUpdateRegistration([confirmedEmail]); + } + } + } + + private patchUpdateRegistration(values: string[]) { + this.subs.push( + this.externalLoginService.patchUpdateRegistration(values, 'email', this.registrationData.id, this.token, 'replace') .pipe(getRemoteDataPayload()) .subscribe((update) => { + // TODO: remove this line (temporary) console.log('Email update:', update); - }); + })); + } + + /** + * Creates a new user from a given token and registration data. + * Based on the registration data, the user will be created with the following properties: + * - email: the email address from the registration data + * - metadata: all metadata values from the registration data, except for the email metadata key (ePerson object does not have an email metadata field) + * - canLogIn: true + * - requireCertificate: false + * @param token The token used to create the user. + * @param registrationData The registration data used to create the user. + * @returns An Observable that emits a boolean indicating whether the user creation was successful. + */ + private postCreateAccountFromToken( + token: string, + registrationData: RegistrationData + ) { + const metadataValues = {}; + for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { + if (hasValue(value[0]?.value) && key !== 'email') { + metadataValues[key] = value[0]; + } } + const eperson = new EPerson(); + eperson.email = registrationData.email; + eperson.metadata = metadataValues; + eperson.canLogIn = true; + eperson.requireCertificate = false; + this.subs.push( + this.epersonDataService.createEPersonForToken(eperson, token).pipe( + getFirstCompletedRemoteData(), + ).subscribe((rd: RemoteData) => { + if (rd.hasFailed) { + this.notificationService.error( + this.translate.get('external-login-page.provide-email.create-account.notifications.error.header'), + this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') + ); + } else if (rd.hasSucceeded) { + // TODO: redirect to ORCID login page + // set Redirect URL to User profile + this.router.navigate(['/login'], { queryParams: { authMethod: registrationData.registrationType } }); + this.authService.setRedirectUrl('/review-account'); + } + })); + } + + ngOnDestroy(): void { + this.subs.filter(sub => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts index 63e64830394..71d56e73cd1 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts @@ -7,17 +7,21 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; +import { ExternalLoginService } from '../../services/external-login.service'; describe('ProvideEmailComponent', () => { let component: ProvideEmailComponent; let fixture: ComponentFixture; + let externalLoginServiceSpy: jasmine.SpyObj; beforeEach(async () => { + const externalLoginService = jasmine.createSpyObj('ExternalLoginService', ['patchUpdateRegistration']); + await TestBed.configureTestingModule({ declarations: [ ProvideEmailComponent ], providers: [ FormBuilder, - { provide: EpersonRegistrationService, useValue: {} }, + { provide: ExternalLoginService, useValue: externalLoginService }, ], imports: [ CommonModule, @@ -36,10 +40,24 @@ describe('ProvideEmailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ProvideEmailComponent); component = fixture.componentInstance; + externalLoginServiceSpy = TestBed.inject(ExternalLoginService) as jasmine.SpyObj; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + // it('should call externalLoginService.patchUpdateRegistration when form is submitted with valid email', () => { + // const email = 'test@example.com'; + // component.emailForm.setValue({ email }); + // component.registrationId = '123'; + // component.token = '456'; + // fixture.detectChanges(); + + // const button = fixture.nativeElement.querySelector('button[type="submit"]'); + // button.click(); + + // expect(externalLoginServiceSpy.patchUpdateRegistration).toHaveBeenCalledWith([email], 'email', component.registrationId, component.token, 'add'); + // }); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index 4b680656392..95e7a41ae91 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -1,7 +1,10 @@ -import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; +import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { getRemoteDataPayload } from '../../../../core/shared/operators'; import { ExternalLoginService } from '../../services/external-login.service'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { Registration } from '../../../../core/shared/registration.model'; +import { Subscription } from 'rxjs'; +import { hasValue } from '../../../../shared/empty.util'; @Component({ selector: 'ds-provide-email', @@ -9,13 +12,22 @@ import { ExternalLoginService } from '../../services/external-login.service'; styleUrls: ['./provide-email.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProvideEmailComponent { +export class ProvideEmailComponent implements OnDestroy { + /** + * The form group for the email input + */ emailForm: FormGroup; - + /** + * The registration id + */ @Input() registrationId: string; - + /** + * The token from the URL + */ @Input() token: string; + subs: Subscription[] = []; + constructor( private formBuilder: FormBuilder, private externalLoginService: ExternalLoginService, @@ -29,11 +41,15 @@ export class ProvideEmailComponent { this.emailForm.markAllAsTouched(); if (this.emailForm.valid) { const email = this.emailForm.get('email').value; - this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'add') - .pipe(getRemoteDataPayload()) - .subscribe((update) => { - console.log('Email update:', update); - }); + this.subs.push(this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'add') + .subscribe((rd: RemoteData) => { + // TODO: remove this line (temporary) + console.log('Email update:', rd); + })); } } + + ngOnDestroy(): void { + this.subs.filter(sub => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + } } diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html index 47a301501bf..bc96b37844d 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -11,7 +11,7 @@

{{ 'external-login.confirmation.header' | translate}}

- + @@ -22,14 +22,14 @@

or

diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 18a7d6ddf98..0322079475f 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -36,11 +36,20 @@ export class LogInComponent implements OnInit, OnDestroy { */ @Input() isStandalonePage: boolean; + /** + * Method to exclude from the list of authentication methods + */ @Input() excludedAuthMethod: AuthMethodType; - + /** + * Weather or not to show the register link + */ @Input() showRegisterLink = true; - @Input() hideAllLinks = false; + /** + * The external login method to force + * the user to use to login while completing the external login process + */ + @Input() externalLoginMethod: AuthMethodType; /** * The list of authentication methods available @@ -77,7 +86,6 @@ export class LogInComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.store.pipe( select(getAuthenticationMethods), ).subscribe(methods => { @@ -87,6 +95,10 @@ export class LogInComponent implements OnInit, OnDestroy { if (hasValue(this.excludedAuthMethod)) { this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType !== this.excludedAuthMethod); } + // if there is an external login method the user should follow, filter the auth methods to only show that one + if (hasValue(this.externalLoginMethod)) { + this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType === this.externalLoginMethod); + } }); // set loading diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c2be335f99f..978ee6782d1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7225,4 +7225,14 @@ "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", + + "external-login.error.notification": "There was an error while processing your request. Please try again later.", + + "external-login.connect-to-existing-account.label": "Connect to an existing user", + + "external-login.modal.label.close": "Close", + + "external-login-page.provide-email.create-account.notifications.error.header": "Something went wrong", + + "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", } diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index c4b8fdaff6e..7c2d5edca4e 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -11307,4 +11307,48 @@ // "external-login-validation.review-account-info.table.header.action": "Override", // TODO New key - Add a translation "external-login-validation.review-account-info.table.header.action": "Override", + + // "on-label": "ON", + // TODO New key - Add a translation + "on-label": "ON", + + // "off-label": "OFF", + // TODO New key - Add a translation + "off-label": "OFF", + + // "review-account-info.merge-data.notification.success": "Your account information has been updated successfully", + // TODO New key - Add a translation + "review-account-info.merge-data.notification.success": "Your account information has been updated successfully", + + // "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", + // TODO New key - Add a translation + "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", + + // "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", + // TODO New key - Add a translation + "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", + + // "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", + // TODO New key - Add a translation + "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", + + // "external-login.error.notification": "There was an error while processing your request. Please try again later.", + // TODO New key - Add a translation + "external-login.error.notification": "There was an error while processing your request. Please try again later.", + + // "external-login.connect-to-existing-account.label": "Connect to an existing user", + // TODO New key - Add a translation + "external-login.connect-to-existing-account.label": "Connect to an existing user", + + // "external-login.modal.label.close": "Close", + // TODO New key - Add a translation + "external-login.modal.label.close": "Close", + + // "external-login-page.provide-email.create-account.notifications.error.header": "Something went wrong", + // TODO New key - Add a translation + "external-login-page.provide-email.create-account.notifications.error.header": "Something went wrong", + + // "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", + // TODO New key - Add a translation + "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", } From c85e8a7175cf4046b98f121c4c8a537e2db9ea5f Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Wed, 4 Oct 2023 10:05:33 +0200 Subject: [PATCH 23/56] [CST-10703] refactor --- src/app/app-routing.module.ts | 5 - .../external-login-page-routing.module.ts | 2 +- .../external-login-page.component.ts | 11 +- ...review-account-info-page-routing.module.ts | 3 +- ...in-review-account-info-page.component.html | 2 +- ...ogin-review-account-info-page.component.ts | 10 +- .../email-validated.component.html | 9 -- .../email-validated.component.scss | 0 .../email-validated.component.spec.ts | 75 ---------- .../email-validated.component.ts | 16 --- ...al-login-validation-page-routing.module.ts | 20 --- ...ernal-login-validation-page.component.html | 10 -- ...ernal-login-validation-page.component.scss | 0 ...al-login-validation-page.component.spec.ts | 112 --------------- ...xternal-login-validation-page.component.ts | 44 ------ .../external-login-validation-page.module.ts | 24 ---- ...stration-data-create-user.resolver.spec.ts | 129 ------------------ .../registration-data-create-user.resolver.ts | 93 ------------- ...xternal-login-validation-page.component.ts | 25 ---- .../confirm-email/confirm-email.component.ts | 19 ++- src/assets/i18n/en.json5 | 6 +- src/assets/i18n/it.json5 | 12 +- 22 files changed, 30 insertions(+), 597 deletions(-) delete mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.html delete mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.scss delete mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts delete mode 100644 src/app/external-login-validation-page/email-validated/email-validated.component.ts delete mode 100644 src/app/external-login-validation-page/external-login-validation-page-routing.module.ts delete mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.html delete mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.scss delete mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.spec.ts delete mode 100644 src/app/external-login-validation-page/external-login-validation-page.component.ts delete mode 100644 src/app/external-login-validation-page/external-login-validation-page.module.ts delete mode 100644 src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts delete mode 100644 src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts delete mode 100644 src/app/external-login-validation-page/themed-external-login-validation-page.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 2fd18766af9..fb4d658d8cb 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -176,11 +176,6 @@ import { RedirectService } from './redirect/redirect.service'; loadChildren: () => import('./external-login-page/external-login-page.module') .then((m) => m.ExternalLoginPageModule) }, - { - path: 'validate-email', - loadChildren: () => import('./external-login-validation-page/external-login-validation-page.module') - .then((m) => m.ExternalLoginValidationPageModule) - }, { path: 'review-account', loadChildren: () => import('./external-login-review-account-info/external-login-review-account-info-page.module') diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index 390db1eaf00..f7425b63d82 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -9,7 +9,7 @@ const routes: Routes = [ path: '', pathMatch: 'full', component: ThemedExternalLoginPageComponent, - // canActivate: [RegistrationTokenGuard], // TODO: uncomment this line to enable the guard later + canActivate: [RegistrationTokenGuard], resolve: { registrationData: RegistrationDataResolver }, }, ]; diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index c8bde7cd0da..fd76a047ba5 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -43,12 +43,11 @@ export class ExternalLoginPageComponent implements OnInit { tap((data) => this.hasErrors = hasNoValue(data.registrationData)), map((data) => data.registrationData)); - // TODO: remove this line (temporary) - this.registrationData$ = of( - mockRegistrationDataModel - ); - this.hasErrors = false; - this.token = '1234567890'; + // this.registrationData$ = of( + // mockRegistrationDataModel + // ); + // this.hasErrors = false; + // this.token = '1234567890'; } } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts index ffec9928371..b83816ece02 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; +import { ReviewAccountGuard } from './helpers/review-account.guard'; const routes: Routes = [ @@ -9,7 +10,7 @@ const routes: Routes = [ path: '', pathMatch: 'full', component: ExternalLoginReviewAccountInfoPageComponent, - // canActivate: [ReviewAccountGuard],// TODO: Remove comment + canActivate: [ReviewAccountGuard], resolve: { registrationData: RegistrationDataResolver } },]; diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html index 97034110392..831b53ce714 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html @@ -8,6 +8,6 @@
diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts index 5c984445031..62da6995fa1 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -43,10 +43,10 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { map((data) => data.registrationData)); // TODO: remove this line (temporary) - this.registrationData$ = of( - mockRegistrationDataModel - ); - this.hasErrors = false; - this.token = '1234567890'; + // this.registrationData$ = of( + // mockRegistrationDataModel + // ); + // this.hasErrors = false; + // this.token = '1234567890'; } } diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.html b/src/app/external-login-validation-page/email-validated/email-validated.component.html deleted file mode 100644 index b58ed085890..00000000000 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.html +++ /dev/null @@ -1,9 +0,0 @@ -

- {{ "external-login.validated-email.header" | translate }} -

- -

- -
- -
diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.scss b/src/app/external-login-validation-page/email-validated/email-validated.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts deleted file mode 100644 index 21c7f5d7d8f..00000000000 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { EmailValidatedComponent } from './email-validated.component'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { CommonModule } from '@angular/common'; -import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock'; -import { AuthService } from 'src/app/core/auth/auth.service'; -import { Router } from '@angular/router'; -import { RouterStub } from 'src/app/shared/testing/router.stub'; -import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; -import { of } from 'rxjs'; -import { AuthServiceMock } from 'src/app/shared/mocks/auth.service.mock'; - -describe('EmailValidatedComponent', () => { - let component: EmailValidatedComponent; - let fixture: ComponentFixture; - let compiledTemplate: HTMLElement; - - const translateServiceStub = { - get: () => of('Mocked Translation Text'), - instant: (key: any) => 'Mocked Translation Text', - onLangChange: new EventEmitter(), - onTranslationChange: new EventEmitter(), - onDefaultLangChange: new EventEmitter() - }; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ EmailValidatedComponent ], - providers: [ - { provide: AuthService, useValue: new AuthServiceMock()}, - { provide: Router, useValue: new RouterStub() }, - { provide: TranslateService, useValue: translateServiceStub }, - ], - imports: [ - CommonModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - schemas: [NO_ERRORS_SCHEMA] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(EmailValidatedComponent); - component = fixture.componentInstance; - compiledTemplate = fixture.nativeElement; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render translated header', () => { - const headerElement = compiledTemplate.querySelector('h4'); - expect(headerElement.textContent).toContain('Mocked Translation Text'); - }); - - it('should render translated info paragraph', () => { - const infoParagraphElement = compiledTemplate.querySelector('p'); - expect(infoParagraphElement.innerHTML).toBeTruthy(); - }); - - it('should render ds-log-in component', () => { - const dsLogInComponent = compiledTemplate.querySelector('ds-log-in'); - expect(dsLogInComponent).toBeTruthy(); - }); -}); - diff --git a/src/app/external-login-validation-page/email-validated/email-validated.component.ts b/src/app/external-login-validation-page/email-validated/email-validated.component.ts deleted file mode 100644 index 8fd45bc4a7f..00000000000 --- a/src/app/external-login-validation-page/email-validated/email-validated.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { AuthService } from '../../core/auth/auth.service'; -@Component({ - selector: 'ds-email-validated', - templateUrl: './email-validated.component.html', - styleUrls: ['./email-validated.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class EmailValidatedComponent { - - constructor(private authService: AuthService) { - // After the user has validated his email, we need to redirect him to the review account page, - // in order to review his account information - this.authService.setRedirectUrl('/review-account'); - } -} diff --git a/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts b/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts deleted file mode 100644 index 241cebebb2f..00000000000 --- a/src/app/external-login-validation-page/external-login-validation-page-routing.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; -import { RegistrationTokenGuard } from '../shared/external-log-in-complete/guards/registration-token.guard'; -import { RegistrationDataCreateUserResolver } from './resolvers/registration-data-create-user.resolver'; -const routes: Routes = [ - { - path: '', - pathMatch: 'full', - component: ThemedExternalLoginValidationPageComponent, - // canActivate: [RegistrationTokenGuard] // TODO: uncomment this line to enable the guard later - resolve: { createUser: RegistrationDataCreateUserResolver }, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ExternalLoginValidationPageRoutingModule { } diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.html b/src/app/external-login-validation-page/external-login-validation-page.component.html deleted file mode 100644 index cfdaed0d6db..00000000000 --- a/src/app/external-login-validation-page/external-login-validation-page.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - - - -
diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.scss b/src/app/external-login-validation-page/external-login-validation-page.component.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts b/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts deleted file mode 100644 index 975fca9f81f..00000000000 --- a/src/app/external-login-validation-page/external-login-validation-page.component.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; -import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; -import { Observable, of } from 'rxjs'; -import { RemoteData } from '../core/data/remote-data'; -import { CommonModule } from '@angular/common'; -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; -import { ActivatedRoute } from '@angular/router'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { AlertType } from '../shared/alert/aletr-type'; -import { tr } from 'date-fns/locale'; - -describe('ExternalLoginValidationPageComponent', () => { - let component: ExternalLoginValidationPageComponent; - let componentAsAny: any; - let fixture: ComponentFixture; - let epersonRegistrationService: any; - - const registrationDataMock = { - registrationType: 'orcid', - email: 'test@test.com', - netId: '0000-0000-0000-0000', - user: 'a44d8c9e-9b1f-4e7f-9b1a-5c9d8a0b1f1a', - registrationMetadata: { - 'email': [{ value: 'test@test.com' }], - 'eperson.lastname': [{ value: 'Doe' }], - 'eperson.firstname': [{ value: 'John' }], - }, - }; - - const tokenMock = 'as552-5a5a5-5a5a5-5a5a5'; - - const routeStub = { - snapshot: { - queryParams: { - token: tokenMock - } - }, - data: of({ - createUser: true, - }), - }; - - beforeEach(async () => { - epersonRegistrationService = { - searchByTokenAndUpdateData: (token: string): Observable> => { return createSuccessfulRemoteDataObject$(registrationDataMock); } - }; - - await TestBed.configureTestingModule({ - declarations: [ExternalLoginValidationPageComponent], - providers: [ - { provide: ActivatedRoute, useValue: routeStub }, - ], - imports: [ - CommonModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock, - }, - }), - ], - schemas: [NO_ERRORS_SCHEMA], - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ExternalLoginValidationPageComponent); - component = fixture.componentInstance; - componentAsAny = component; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should set validationFailed to true if createUser is falsy', () => { - const activatedRoute = TestBed.inject(ActivatedRoute); - activatedRoute.data = of({ createUser: false }); - component.ngOnInit(); - expect(componentAsAny.validationFailed.value).toBeTrue(); - }); - - it('should set validationFailed to false if createUser is truthy', () => { - const activatedRoute = TestBed.inject(ActivatedRoute); - activatedRoute.data = of({ createUser: true }); - component.ngOnInit(); - expect(componentAsAny.validationFailed.value).toBeFalse(); - }); - - it('should show DsEmailValidatedComponent when hasFailed() returns false', () => { - spyOn(component, 'hasFailed').and.returnValue(of(false)); - fixture.detectChanges(); - const dsEmailValidated = fixture.nativeElement.querySelector('ds-email-validated'); - expect(dsEmailValidated).toBeTruthy(); - }); - - it('should show DsAlertComponent when hasFailed() returns true', () => { - spyOn(component, 'hasFailed').and.returnValue(of(true)); - fixture.detectChanges(); - const dsAlert = fixture.nativeElement.querySelector('ds-alert'); - expect(dsAlert).toBeTruthy(); - }); - - afterEach(() => { - fixture.destroy(); - }); -}); diff --git a/src/app/external-login-validation-page/external-login-validation-page.component.ts b/src/app/external-login-validation-page/external-login-validation-page.component.ts deleted file mode 100644 index abcd256f8a7..00000000000 --- a/src/app/external-login-validation-page/external-login-validation-page.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { AlertType } from '../shared/alert/aletr-type'; -import { BehaviorSubject, Observable } from 'rxjs'; - -@Component({ - templateUrl: './external-login-validation-page.component.html', - styleUrls: ['./external-login-validation-page.component.scss'], -}) -export class ExternalLoginValidationPageComponent implements OnInit { - /** - * The type of alert to show - */ - public AlertTypeEnum = AlertType; - - /** - * Whether the component has errors - */ - private validationFailed: BehaviorSubject = - new BehaviorSubject(false); - - constructor( - private arouter: ActivatedRoute, - ) { - } - - ngOnInit(): void { - this.arouter.data.subscribe((data) => { - const resolvedData = data.createUser; - this.validationFailed.next(!resolvedData); - }); - - // TODO: remove this line (temporary) - this.validationFailed.next(false); - } - - - /** - * Check if the validation has failed - */ - public hasFailed(): Observable { - return this.validationFailed.asObservable(); - } -} diff --git a/src/app/external-login-validation-page/external-login-validation-page.module.ts b/src/app/external-login-validation-page/external-login-validation-page.module.ts deleted file mode 100644 index 3bad614d7e6..00000000000 --- a/src/app/external-login-validation-page/external-login-validation-page.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { ExternalLoginValidationPageRoutingModule } from './external-login-validation-page-routing.module'; -import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; -import { ThemedExternalLoginValidationPageComponent } from './themed-external-login-validation-page.component'; - -import { EmailValidatedComponent } from './email-validated/email-validated.component'; -import { SharedModule } from '../shared/shared.module'; - - -@NgModule({ - declarations: [ - ExternalLoginValidationPageComponent, - ThemedExternalLoginValidationPageComponent, - EmailValidatedComponent, - ], - imports: [ - CommonModule, - ExternalLoginValidationPageRoutingModule, - SharedModule - ] -}) -export class ExternalLoginValidationPageModule { } diff --git a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts deleted file mode 100644 index 979bdc89491..00000000000 --- a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { RegistrationDataCreateUserResolver } from './registration-data-create-user.resolver'; -import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; -import { RouterStateSnapshot } from '@angular/router'; -import { EPerson } from '../../core/eperson/models/eperson.model'; -import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; -import { Registration } from '../../core/shared/registration.model'; -import { MetadataValue } from 'src/app/core/shared/metadata.models'; - -describe('RegistrationDataCreateUserResolver', () => { - let resolver: RegistrationDataCreateUserResolver; - let epersonRegistrationService: jasmine.SpyObj; - let epersonDataService: jasmine.SpyObj; - - const registrationDataMock = { - registrationType: 'orcid', - email: 'test@test.com', - netId: '0000-0000-0000-0000', - user: null, - registrationMetadata: { - 'email': [{ value: 'test@test.com' }], - 'eperson.lastname': [{ value: 'Doe' }], - 'eperson.firstname': [{ value: 'John' }], - }, - }; - - const tokenMock = 'as552-5a5a5-5a5a5-5a5a5'; - - - beforeEach(() => { - const spyEpersonRegistrationService = jasmine.createSpyObj('EpersonRegistrationService', [ - 'searchByTokenAndUpdateData' - ]); - const spyEpersonDataService = jasmine.createSpyObj('EPersonDataService', [ - 'createEPersonForToken' - ]); - - TestBed.configureTestingModule({ - providers: [ - RegistrationDataCreateUserResolver, - { provide: EpersonRegistrationService, useValue: spyEpersonRegistrationService }, - { provide: EPersonDataService, useValue: spyEpersonDataService } - ] - }); - resolver = TestBed.inject(RegistrationDataCreateUserResolver); - epersonRegistrationService = TestBed.inject(EpersonRegistrationService) as jasmine.SpyObj; - epersonDataService = TestBed.inject(EPersonDataService) as jasmine.SpyObj; - }); - - it('should be created', () => { - expect(resolver).toBeTruthy(); - }); - - describe('resolve', () => { - const token = tokenMock; - const route = { params: { token: token } } as any; - const state = {} as RouterStateSnapshot; - - it('should create a new user and return true when registration data does not contain a user', (done) => { - const registration = new Registration(); - epersonRegistrationService.searchByTokenAndUpdateData.and.returnValue(createSuccessfulRemoteDataObject$(registration)); - - const createdEPerson = new EPerson(); - createdEPerson.email = registrationDataMock.email; - createdEPerson.metadata = { - 'eperson.lastname': [Object.assign(new MetadataValue(), { value: 'Doe' })], - 'eperson.firstname': [Object.assign(new MetadataValue(), { value: 'John' })], - }; - createdEPerson.canLogIn = true; - createdEPerson.requireCertificate = false; - epersonDataService.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(createdEPerson)); - - resolver.resolve(route, state).subscribe((result) => { - expect(result).toBeTrue(); - expect(epersonRegistrationService.searchByTokenAndUpdateData).toHaveBeenCalledWith(token); - expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(createdEPerson, token); - done(); - }); - }); - - it('should return false when search by token and update data fails', (done) => { - epersonRegistrationService.searchByTokenAndUpdateData.and.returnValue(createFailedRemoteDataObject$()); - - resolver.resolve(route, state).subscribe((result) => { - expect(result).toBeFalse(); - expect(epersonRegistrationService.searchByTokenAndUpdateData).toHaveBeenCalledWith(token); - expect(epersonDataService.createEPersonForToken).not.toHaveBeenCalled(); - done(); - }); - }); - - }); - - describe('createUserFromToken', () => { - const token = tokenMock; - const registrationData: RegistrationData = Object.assign(new RegistrationData(), registrationDataMock); - - it('should create a new user and return true', (done) => { - const createdEPerson = new EPerson(); - createdEPerson.email = registrationData.email; - createdEPerson.metadata = { - 'eperson.lastname': 'Doe', //[{ value: 'Doe' }], - 'eperson.firstname': 'John',// [{ value: 'John' }], - } as any; - createdEPerson.canLogIn = true; - createdEPerson.requireCertificate = false; - epersonDataService.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(createdEPerson)); - - resolver.createUserFromToken(token, registrationData).subscribe((result) => { - expect(result).toBeTrue(); - expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(createdEPerson, token); - done(); - }); - }); - - it('should return false when create EPerson for token fails', (done) => { - epersonDataService.createEPersonForToken.and.returnValue(createFailedRemoteDataObject$()); - - resolver.createUserFromToken(token, registrationData).subscribe((result) => { - expect(result).toBeFalse(); - expect(epersonDataService.createEPersonForToken).toHaveBeenCalledWith(jasmine.any(EPerson), token); - done(); - }); - }); - }); -}); diff --git a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts b/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts deleted file mode 100644 index 000422f7623..00000000000 --- a/src/app/external-login-validation-page/resolvers/registration-data-create-user.resolver.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - Resolve, - RouterStateSnapshot, - ActivatedRouteSnapshot, -} from '@angular/router'; -import { Observable, map, of, switchMap } from 'rxjs'; -import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; -import { RemoteData } from '../../core/data/remote-data'; -import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { EPerson } from '../../core/eperson/models/eperson.model'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { hasValue, hasNoValue } from '../../shared/empty.util'; -import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; - -@Injectable({ - providedIn: 'root', -}) -export class RegistrationDataCreateUserResolver implements Resolve { - constructor( - private epersonRegistrationService: EpersonRegistrationService, - private epersonDataService: EPersonDataService - ) {} - - /** - * Resolves the registration data and creates a new user account from the given token. - * The account will be created only when the registration data does NOT contain a user (uuid). - * @param route The activated route snapshot. - * @param state The router state snapshot. - * @returns An observable of a boolean indicating whether the user was created successfully or not. - */ - resolve( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot - ): Observable { - const token = route.params.token; - return this.epersonRegistrationService - .searchByTokenAndUpdateData(token) - .pipe( - getFirstCompletedRemoteData(), - switchMap((rd) => { - if ( - rd.hasSucceeded && - hasValue(rd.payload) && - hasNoValue(rd.payload.user) - ) { - const registrationData = Object.assign( - new RegistrationData(), - rd.payload - ); - return this.createUserFromToken(token, registrationData); - } - if (rd.hasFailed) { - return of(false); - } - }) - ); - } - - /** - * Creates a new user from a given token and registration data. - * Based on the registration data, the user will be created with the following properties: - * - email: the email address from the registration data - * - metadata: all metadata values from the registration data, except for the email metadata key (ePerson object does not have an email metadata field) - * - canLogIn: true - * - requireCertificate: false - * @param token The token used to create the user. - * @param registrationData The registration data used to create the user. - * @returns An Observable that emits a boolean indicating whether the user creation was successful. - */ - createUserFromToken( - token: string, - registrationData: RegistrationData - ): Observable { - const metadataValues = {}; - for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { - if (hasValue(value[0]?.value) && key !== 'email') { - metadataValues[key] = value[0]?.value; - } - } - const eperson = new EPerson(); - eperson.email = registrationData.email; - eperson.metadata = metadataValues; - eperson.canLogIn = true; - eperson.requireCertificate = false; - return this.epersonDataService.createEPersonForToken(eperson, token).pipe( - getFirstCompletedRemoteData(), - map((rd: RemoteData) => { - return rd.hasSucceeded; - }) - ); - } -} diff --git a/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts b/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts deleted file mode 100644 index a08b2669deb..00000000000 --- a/src/app/external-login-validation-page/themed-external-login-validation-page.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from '@angular/core'; -import { ThemedComponent } from '../shared/theme-support/themed.component'; -import { ExternalLoginValidationPageComponent } from './external-login-validation-page.component'; - -/** - * Themed wrapper for ExternalLoginValidationPageComponent - */ -@Component({ - selector: 'ds-themed-external-login-page', - styleUrls: [], - templateUrl: './../shared/theme-support/themed.component.html' -}) -export class ThemedExternalLoginValidationPageComponent extends ThemedComponent { - protected getComponentName(): string { - return 'ExternalLoginValidationPageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../themes/${themeName}/app/external-login-validation-page/external-login-validation-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./external-login-validation-page.component`); - } -} diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index d4e470f9e72..45683aaee5b 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -21,13 +21,22 @@ import { Subscription } from 'rxjs'; changeDetection: ChangeDetectionStrategy.OnPush }) export class ConfirmEmailComponent implements OnDestroy { - + /** + * The form containing the email input + */ emailForm: FormGroup; - + /** + * The registration data object + */ @Input() registrationData: RegistrationData; + /** + * The token to be used to confirm the registration + */ @Input() token: string; - + /** + * The subscriptions to unsubscribe from + */ subs: Subscription[] = []; constructor( @@ -46,8 +55,6 @@ export class ConfirmEmailComponent implements OnDestroy { submitForm() { this.emailForm.markAllAsTouched(); - this.router.navigate(['/login'], { queryParams: { authMethod: this.registrationData.registrationType } }); - if (this.emailForm.valid) { const confirmedEmail = this.emailForm.get('email').value; if (confirmedEmail && isEqual(this.registrationData.email, confirmedEmail.trim())) { @@ -104,7 +111,7 @@ export class ConfirmEmailComponent implements OnDestroy { this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') ); } else if (rd.hasSucceeded) { - // TODO: redirect to ORCID login page + // redirect to ORCID login page // set Redirect URL to User profile this.router.navigate(['/login'], { queryParams: { authMethod: registrationData.registrationType } }); this.authService.setRedirectUrl('/review-account'); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 978ee6782d1..94a153d1772 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7194,10 +7194,6 @@ "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", - "external-login.validated-email.header": "Email validated", - - "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", - "external-login.provide-email.header": "Provide email", "external-login.provide-email.button.label": "Send Verification link", @@ -7222,7 +7218,7 @@ "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", - "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", + "review-account-info.alert.error.content": "Something went wrong. Please try again later.", "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 7c2d5edca4e..fd5fb8ca8b5 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -11268,14 +11268,6 @@ // TODO New key - Add a translation "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", - // "external-login.validated-email.header": "Email validated", - // TODO New key - Add a translation - "external-login.validated-email.header": "Email validated", - - // "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", - // TODO New key - Add a translation - "external-login.validated-email.info": "Your email has been validated.
You can now login in the system with your prefered authentication method.", - // "external-login.provide-email.header": "Provide email", // TODO New key - Add a translation "external-login.provide-email.header": "Provide email", @@ -11324,9 +11316,9 @@ // TODO New key - Add a translation "review-account-info.merge-data.notification.error": "Something went wrong while updating your account information", - // "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", + // "review-account-info.alert.error.content": "Something went wrong. Please try again later.", // TODO New key - Add a translation - "external-login.validate-email.no-token": "Something went wrong. Your email address is not validated succesfully.", + "review-account-info.alert.error.content": "Something went wrong. Please try again later.", // "external-login-page.provide-email.notifications.error": "Something went wrong.Email address was omitted or the operation is not valid.", // TODO New key - Add a translation From 7d59f3c87fa6d7acdc734a20a5a1058568841141 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 11:10:20 +0200 Subject: [PATCH 24/56] [CST-10703] unit tests --- .../core/eperson/eperson-data.service.spec.ts | 16 +++ ...-email-confirmation-page-routing.module.ts | 4 +- .../external-login-page-routing.module.ts | 4 +- .../external-login-page.component.ts | 10 +- ...review-account-info-page-routing.module.ts | 18 +-- ...review-account-info-page.component.spec.ts | 1 - ...ogin-review-account-info-page.component.ts | 11 +- .../helpers/review-account.guard.spec.ts | 105 +++++++------- .../review-account-info.component.spec.ts | 57 ++------ .../confirm-email.component.spec.ts | 135 ++++++++++++++++-- .../confirm-email/confirm-email.component.ts | 15 +- .../provide-email.component.spec.ts | 1 - .../provide-email/provide-email.component.ts | 8 +- .../external-log-in.component.html | 2 +- .../external-log-in.component.spec.ts | 35 ++--- .../registration-data.resolver.spec.ts | 33 ++++- .../services/external-login.service.spec.ts | 62 +++++++- 17 files changed, 347 insertions(+), 170 deletions(-) diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index b4b939eebf4..30ec39aa0d3 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -28,6 +28,7 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { createPaginatedList, createRequestEntry$ } from '../../shared/testing/utils.test'; import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; +import { RemoteData } from '../data/remote-data'; describe('EPersonDataService', () => { let service: EPersonDataService; @@ -314,6 +315,21 @@ describe('EPersonDataService', () => { }); }); + describe('mergeEPersonDataWithToken', () => { + const uuid = '1234-5678-9012-3456'; + const token = 'abcd-efgh-ijkl-mnop'; + const metadataKey = 'eperson.firstname'; + beforeEach(() => { + spyOn(service, 'mergeEPersonDataWithToken').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock)); + }); + + it('should merge EPerson data with token', () => { + service.mergeEPersonDataWithToken(uuid, token, metadataKey).subscribe((result: RemoteData) => { + expect(result.hasSucceeded).toBeTrue(); + }); + expect(service.mergeEPersonDataWithToken).toHaveBeenCalledWith(uuid, token, metadataKey); + }); + }); }); class DummyChangeAnalyzer implements ChangeAnalyzer { diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts index e242d9ea1a9..0033d1620e5 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page-routing.module.ts @@ -12,6 +12,6 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], }) -export class ExternalLoginEmailConfirmationPageRoutingModule { } +export class ExternalLoginEmailConfirmationPageRoutingModule {} diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index f7425b63d82..21bbb7ee920 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -17,6 +17,6 @@ const routes: Routes = [ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], - providers: [] + providers: [], }) -export class ExternalLoginPageRoutingModule { } +export class ExternalLoginPageRoutingModule {} diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index fd76a047ba5..27e08e4e5ae 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -2,9 +2,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { hasNoValue } from '../shared/empty.util'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; -import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; import { AlertType } from '../shared/alert/aletr-type'; -import { Observable, first, map, of, tap } from 'rxjs'; +import { Observable, first, map, tap } from 'rxjs'; @Component({ templateUrl: './external-login-page.component.html', @@ -42,12 +41,5 @@ export class ExternalLoginPageComponent implements OnInit { first(), tap((data) => this.hasErrors = hasNoValue(data.registrationData)), map((data) => data.registrationData)); - - // TODO: remove this line (temporary) - // this.registrationData$ = of( - // mockRegistrationDataModel - // ); - // this.hasErrors = false; - // this.token = '1234567890'; } } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts index b83816ece02..1119c9fc49b 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts @@ -4,18 +4,18 @@ import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-re import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; import { ReviewAccountGuard } from './helpers/review-account.guard'; - const routes: Routes = [ { - path: '', - pathMatch: 'full', - component: ExternalLoginReviewAccountInfoPageComponent, - canActivate: [ReviewAccountGuard], - resolve: { registrationData: RegistrationDataResolver } -},]; + path: '', + pathMatch: 'full', + component: ExternalLoginReviewAccountInfoPageComponent, + canActivate: [ReviewAccountGuard], + resolve: { registrationData: RegistrationDataResolver }, + }, +]; @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], }) -export class ExternalLoginReviewAccountInfoRoutingModule { } +export class ExternalLoginReviewAccountInfoRoutingModule {} diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts index 7622f081d0d..ca65204e8dd 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; -import { AlertType } from '../shared/alert/aletr-type'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts index 62da6995fa1..6e0cab84594 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit } from '@angular/core'; import { AlertType } from '../shared/alert/aletr-type'; -import { Observable, first, map, of, tap } from 'rxjs'; +import { Observable, first, map, tap } from 'rxjs'; import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { ActivatedRoute } from '@angular/router'; import { hasNoValue } from '../shared/empty.util'; -import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; @Component({ templateUrl: './external-login-review-account-info-page.component.html', @@ -36,17 +35,9 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { this.token = this.arouter.snapshot.queryParams.token; } - ngOnInit(): void { this.registrationData$ = this.arouter.data.pipe(first(), tap((data) => this.hasErrors = hasNoValue(data?.registrationData)), map((data) => data.registrationData)); - - // TODO: remove this line (temporary) - // this.registrationData$ = of( - // mockRegistrationDataModel - // ); - // this.hasErrors = false; - // this.token = '1234567890'; } } diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts index 61cf3f178cc..03e8d53343c 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts @@ -1,79 +1,78 @@ import { TestBed } from '@angular/core/testing'; import { ReviewAccountGuard } from './review-account.guard'; import { ActivatedRoute, convertToParamMap, Params, Router } from '@angular/router'; -import { of as observableOf } from 'rxjs'; -import { AuthService } from 'src/app/core/auth/auth.service'; -import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; -import { EPerson } from 'src/app/core/eperson/models/eperson.model'; -import { Registration } from 'src/app/core/shared/registration.model'; -import { RouterMock } from 'src/app/shared/mocks/router.mock'; -import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; +import { of as observableOf, of } from 'rxjs'; +import { AuthService } from '../../core/auth/auth.service'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; +import { AuthMethodType } from '../../core/auth/models/auth.method-type'; describe('ReviewAccountGuard', () => { let guard: ReviewAccountGuard; + let epersonRegistrationService: any; + let authService: any; + const route = new RouterMock(); - const registrationWithGroups = Object.assign(new Registration(), + const registrationMock = Object.assign(new RegistrationData(), { email: 'test@email.org', - token: 'test-token', + registrationType: AuthMethodType.Validation + }); - const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { - searchRegistrationByToken: createSuccessfulRemoteDataObject$(registrationWithGroups) - }); - const authService = { - getAuthenticatedUserFromStore: () => observableOf(ePerson), - setRedirectUrl: () => { - return true; - } - } as any; - const ePerson = Object.assign(new EPerson(), { - id: 'test-eperson', - uuid: 'test-eperson' - }); + beforeEach(() => { const paramObject: Params = {}; paramObject.token = '1234'; + epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { + searchRegistrationByToken: createSuccessfulRemoteDataObject$(registrationMock) + }); + authService = { + isAuthenticated: () => observableOf(true) + } as any; TestBed.configureTestingModule({ - providers: [{provide: Router, useValue: route}, - { - provide: ActivatedRoute, - useValue: { - queryParamMap: observableOf(convertToParamMap(paramObject)) - }, + providers: [{ provide: Router, useValue: route }, + { + provide: ActivatedRoute, + useValue: { + queryParamMap: observableOf(convertToParamMap(paramObject)) }, - {provide: EpersonRegistrationService, useValue: epersonRegistrationService}, - {provide: AuthService, useValue: authService} + }, + { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, + { provide: AuthService, useValue: authService } ] }); - guard = TestBed.get(ReviewAccountGuard); + guard = TestBed.inject(ReviewAccountGuard); }); it('should be created', () => { expect(guard).toBeTruthy(); }); - describe('based on the response of "searchByToken have', () => { - it('can activate must return true when registration data includes groups', () => { - (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) - .subscribe( - (canActivate) => { - expect(canActivate).toEqual(true); - } - ); - }); - it('can activate must return false when registration data includes groups', () => { - const registrationWithDifferentUsedFromLoggedInt = Object.assign(new Registration(), - { - email: 't1@email.org', - token: 'test-token', - }); - epersonRegistrationService.searchRegistrationByToken.and.returnValue(observableOf(registrationWithDifferentUsedFromLoggedInt)); - (guard.canActivate({ params: { token: '123456789' } } as any, {} as any) as any) - .subscribe( - (canActivate) => { - expect(canActivate).toEqual(false); - } - ); + + it('can activate must return true when registration type is validation', () => { + (guard.canActivate({ params: { token: 'valid token' } } as any, {} as any) as any) + .subscribe( + (canActivate) => { + expect(canActivate).toEqual(true); + } + ); + }); + + it('should navigate to 404 if the registration search fails', () => { + epersonRegistrationService.searchRegistrationByToken.and.returnValue(createFailedRemoteDataObject$()); + (guard.canActivate({ params: { token: 'invalid-token' } } as any, {} as any) as any).subscribe((result) => { + expect(result).toBeFalse(); + expect(route.navigate).toHaveBeenCalledWith(['/404']); }); + }); + it('should navigate to 404 if the registration type is not validation and the user is not authenticated', () => { + registrationMock.registrationType = AuthMethodType.Password; + epersonRegistrationService.searchRegistrationByToken.and.returnValue(createSuccessfulRemoteDataObject$(registrationMock)); + spyOn(authService, 'isAuthenticated').and.returnValue(of(false)); + (guard.canActivate({ params: { token: 'invalid-token' } } as any, {} as any) as any).subscribe((result) => { + expect(route.navigate).toHaveBeenCalledWith(['/404']); + }); }); }); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index 7e1d3f08de3..70185c37543 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -100,7 +100,6 @@ describe('ReviewAccountInfoComponent', () => { registrationDataMock ); component.registrationToken = 'test-token'; - spyOn(router, 'navigate'); fixture.detectChanges(); }); @@ -108,12 +107,6 @@ describe('ReviewAccountInfoComponent', () => { expect(component).toBeTruthy(); }); - it('should call getEPersonData when ngOnInit is called', () => { - spyOn(component, 'getEPersonData'); - component.ngOnInit(); - expect(component.getEPersonData).toHaveBeenCalled(); - }); - it('should prepare data to compare', () => { component.ngOnInit(); const dataToCompare = component.dataToCompare; @@ -125,16 +118,6 @@ describe('ReviewAccountInfoComponent', () => { expect(dataToCompare[0].receivedValue).toBe('test@test.com'); }); - it('should get EPerson data', fakeAsync(() => { - spyOn(ePersonDataServiceStub, 'findById').and.returnValue( - of({ payload: mockEPerson } as RemoteData) - ); - component.getEPersonData(); - tick(); - expect(ePersonDataServiceStub.findById).toHaveBeenCalledWith(registrationDataMock.user); - expect(component.epersonCurrentData).toEqual(EPersonMock); - })); - it('should update dataToCompare when overrideValue is changed', () => { component.onOverrideChange(true, 'email'); expect(component.dataToCompare[0].overrideValue).toBe(true); @@ -173,30 +156,6 @@ describe('ReviewAccountInfoComponent', () => { expect(router.navigate).toHaveBeenCalledWith(['/profile']); })); - it('should merge EPerson data with token when overrideValue is false', fakeAsync(() => { - spyOn(ePersonDataServiceStub, 'mergeEPersonDataWithToken').and.returnValue( - of({ hasSucceeded: true }) - ); - component.mergeEPersonDataWithToken(); - tick(); - expect(ePersonDataServiceStub.mergeEPersonDataWithToken).toHaveBeenCalledTimes(1); - expect(router.navigate).toHaveBeenCalledWith(['/profile']); - })); - - - it('should unsubscribe from subscriptions when ngOnDestroy is called', () => { - const subscription1 = jasmine.createSpyObj('Subscription', [ - 'unsubscribe', - ]); - const subscription2 = jasmine.createSpyObj('Subscription', [ - 'unsubscribe', - ]); - component.subs = [subscription1, subscription2]; - component.ngOnDestroy(); - expect(subscription1.unsubscribe).toHaveBeenCalled(); - expect(subscription2.unsubscribe).toHaveBeenCalled(); - }); - it('should display registration data', () => { const registrationTypeElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child th'); const netIdElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child td'); @@ -214,10 +173,9 @@ describe('ReviewAccountInfoComponent', () => { const firstDataLabel: HTMLElement = firstDataRow.querySelector('th'); const firstDataReceivedValue: HTMLElement = firstDataRow.querySelectorAll('td')[0]; const firstDataOverrideSwitch: HTMLElement = firstDataRow.querySelector('ui-switch'); - expect(firstDataLabel.textContent.trim()).toBe('Lastname'); expect(firstDataReceivedValue.textContent.trim()).toBe('Doe'); - expect(firstDataOverrideSwitch).not.toBeNull(); + expect(firstDataOverrideSwitch).toBeNull(); }); it('should trigger onSave() when the button is clicked', () => { @@ -227,6 +185,19 @@ describe('ReviewAccountInfoComponent', () => { expect(component.onSave).toHaveBeenCalled(); }); + it('should unsubscribe from subscriptions when ngOnDestroy is called', () => { + const subscription1 = jasmine.createSpyObj('Subscription', [ + 'unsubscribe', + ]); + const subscription2 = jasmine.createSpyObj('Subscription', [ + 'unsubscribe', + ]); + component.subs = [subscription1, subscription2]; + component.ngOnDestroy(); + expect(subscription1.unsubscribe).toHaveBeenCalled(); + expect(subscription2.unsubscribe).toHaveBeenCalled(); + }); + afterEach(() => { fixture.destroy(); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index f26593040f9..1a43180bbc0 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -2,46 +2,159 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmEmailComponent } from './confirm-email.component'; import { FormBuilder } from '@angular/forms'; -import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; import { CommonModule } from '@angular/common'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; +import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { EPersonDataService } from 'src/app/core/eperson/eperson-data.service'; +import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; +import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; +import { RegistrationData } from '../../models/registration-data.model'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; +import { EPerson } from 'src/app/core/eperson/models/eperson.model'; +import { Router } from '@angular/router'; +import { RouterMock } from 'src/app/shared/mocks/router.mock'; +import { of } from 'rxjs'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; let fixture: ComponentFixture; + let externalLoginServiceSpy: jasmine.SpyObj; + let epersonDataServiceSpy: jasmine.SpyObj; + let notificationServiceSpy: jasmine.SpyObj; + let authServiceSpy: jasmine.SpyObj; + let router; + const translateServiceStub = { + get: () => of(''), + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; beforeEach(async () => { + externalLoginServiceSpy = jasmine.createSpyObj('ExternalLoginService', [ + 'patchUpdateRegistration', + ]); + epersonDataServiceSpy = jasmine.createSpyObj('EPersonDataService', [ + 'createEPersonForToken', + ]); + notificationServiceSpy = jasmine.createSpyObj('NotificationsService', [ + 'error', + ]); + authServiceSpy = jasmine.createSpyObj('AuthService', ['setRedirectUrl']); + router = new RouterMock(); await TestBed.configureTestingModule({ - declarations: [ ConfirmEmailComponent ], + declarations: [ConfirmEmailComponent], providers: [ FormBuilder, - { provide: EpersonRegistrationService, useValue: {} }, - { provide: ExternalLoginService, useValue: {} }, + { provide: ExternalLoginService, useValue: externalLoginServiceSpy }, + { provide: EPersonDataService, useValue: epersonDataServiceSpy }, + { provide: NotificationsService, useValue: notificationServiceSpy }, + { provide: AuthService, useValue: authServiceSpy }, + { provide: Router, useValue: router }, + { provide: TranslateService, useValue: translateServiceStub } ], imports: [ CommonModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useClass: TranslateLoaderMock - } + useClass: TranslateLoaderMock, + }, }), ], - schemas: [NO_ERRORS_SCHEMA] - }) - .compileComponents(); + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ConfirmEmailComponent); component = fixture.componentInstance; + component.registrationData = Object.assign(new RegistrationData(), { + id: '123', + email: 'test@example.com', + registrationMetadata: {}, + registrationType: AuthMethodType.Orcid, + }); + component.token = 'test-token'; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + describe('submitForm', () => { + it('should call postCreateAccountFromToken if email is confirmed', () => { + component.emailForm.setValue({ email: 'test@example.com' }); + spyOn(component as any, 'postCreateAccountFromToken'); + component.submitForm(); + expect((component as any).postCreateAccountFromToken).toHaveBeenCalledWith( + 'test-token', + component.registrationData + ); + }); + + it('should call patchUpdateRegistration if email is not confirmed', () => { + component.emailForm.setValue({ email: 'new-email@example.com' }); + spyOn(component as any, 'patchUpdateRegistration'); + component.submitForm(); + expect((component as any).patchUpdateRegistration).toHaveBeenCalledWith([ + 'new-email@example.com', + ]); + }); + + it('should not call any methods if form is invalid', () => { + component.emailForm.setValue({ email: 'invalid-email' }); + spyOn(component as any, 'postCreateAccountFromToken'); + spyOn(component as any, 'patchUpdateRegistration'); + component.submitForm(); + expect((component as any).postCreateAccountFromToken).not.toHaveBeenCalled(); + expect((component as any).patchUpdateRegistration).not.toHaveBeenCalled(); + }); + }); + + describe('postCreateAccountFromToken', () => { + it('should call epersonDataService.createEPersonForToken with correct arguments', () => { + epersonDataServiceSpy.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(new EPerson())); + (component as any).postCreateAccountFromToken( + 'test-token', + component.registrationData + ); + expect(epersonDataServiceSpy.createEPersonForToken).toHaveBeenCalledWith( + jasmine.any(Object), + 'test-token' + ); + }); + + it('should show error notification if user creation fails', () => { + epersonDataServiceSpy.createEPersonForToken.and.returnValue( + createFailedRemoteDataObject$() + ); + (component as any).postCreateAccountFromToken( + 'test-token', + component.registrationData + ); + expect(notificationServiceSpy.error).toHaveBeenCalled(); + }); + + it('should redirect to login page if user creation succeeds', () => { + epersonDataServiceSpy.createEPersonForToken.and.returnValue( + createSuccessfulRemoteDataObject$(new EPerson()) + ); + (component as any).postCreateAccountFromToken( + 'test-token', + component.registrationData + ); + expect((component as any).router.navigate).toHaveBeenCalledWith(['/login'], { + queryParams: { authMethod: 'orcid' }, + }); + }); + }); + + afterEach(() => { + fixture.destroy(); + }); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 45683aaee5b..449870f420b 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -53,6 +53,12 @@ export class ConfirmEmailComponent implements OnDestroy { }); } + + /** + * Submits the email form and performs appropriate actions based on the form's validity and user input. + * If the form is valid and the confirmed email matches the registration email, calls the postCreateAccountFromToken method with the token and registration data. + * If the form is valid but the confirmed email does not match the registration email, calls the patchUpdateRegistration method with the confirmed email. + */ submitForm() { this.emailForm.markAllAsTouched(); if (this.emailForm.valid) { @@ -65,6 +71,11 @@ export class ConfirmEmailComponent implements OnDestroy { } } + /** + * Sends a PATCH request to update the user's registration with the given values. + * @param values - The values to update the user's registration with. + * @returns An Observable that emits the updated registration data. + */ private patchUpdateRegistration(values: string[]) { this.subs.push( this.externalLoginService.patchUpdateRegistration(values, 'email', this.registrationData.id, this.token, 'replace') @@ -111,8 +122,8 @@ export class ConfirmEmailComponent implements OnDestroy { this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') ); } else if (rd.hasSucceeded) { - // redirect to ORCID login page - // set Redirect URL to User profile + // redirect to login page with authMethod query param, so that the login page knows which authentication method to use + // set Redirect URL to User profile, so the user is redirected to the profile page after logging in this.router.navigate(['/login'], { queryParams: { authMethod: registrationData.registrationType } }); this.authService.setRedirectUrl('/review-account'); } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts index 71d56e73cd1..3ccb0a189a5 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts @@ -6,7 +6,6 @@ import { CommonModule } from '@angular/common'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { EpersonRegistrationService } from '../../../../core/data/eperson-registration.service'; import { ExternalLoginService } from '../../services/external-login.service'; describe('ProvideEmailComponent', () => { diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index 95e7a41ae91..92faf10e756 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -25,7 +25,9 @@ export class ProvideEmailComponent implements OnDestroy { * The token from the URL */ @Input() token: string; - + /** + * The subscriptions to unsubscribe from + */ subs: Subscription[] = []; constructor( @@ -37,6 +39,10 @@ export class ProvideEmailComponent implements OnDestroy { }); } + /** + * Updates the user's email in the registration data. + * @returns void + */ submitForm() { this.emailForm.markAllAsTouched(); if (this.emailForm.valid) { diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html index bc96b37844d..2bd3b736774 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -21,7 +21,7 @@

{{ 'external-login.confirmation.header' | translate}}

or

-
diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts index cbd8beeb1bb..f34a7883d1b 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts @@ -18,7 +18,7 @@ import { RegistrationData } from '../models/registration-data.model'; describe('ExternalLogInComponent', () => { let component: ExternalLogInComponent; let fixture: ComponentFixture; - let compiledTemplate: HTMLElement; + let modalService: NgbModal = jasmine.createSpyObj('modalService', ['open']); const registrationDataMock = { id: '3', @@ -53,13 +53,7 @@ describe('ExternalLogInComponent', () => { { provide: TranslateService, useValue: translateServiceStub }, { provide: Injector, useValue: {} }, { provide: AuthService, useValue: new AuthServiceMock() }, - { - provide: NgbModal, useValue: { - open: () => { - /*comment*/ - } - } - }, + { provide: NgbModal, useValue: modalService }, FormBuilder ], imports: [ @@ -80,7 +74,6 @@ describe('ExternalLogInComponent', () => { component = fixture.componentInstance; component.registrationData = Object.assign(new RegistrationData, registrationDataMock); component.registrationType = registrationDataMock.registrationType; - compiledTemplate = fixture.nativeElement; fixture.detectChanges(); }); @@ -88,31 +81,27 @@ describe('ExternalLogInComponent', () => { expect(component).toBeTruthy(); }); + beforeEach(() => { + component.registrationData = Object.assign(new RegistrationData(), registrationDataMock, { email: 'user@institution.edu' }); + fixture.detectChanges(); + }); + it('should set registrationType and informationText correctly when email is present', () => { expect(component.registrationType).toBe(registrationDataMock.registrationType); expect(component.informationText).toBeDefined(); }); it('should render the template to confirm email when registrationData has email', () => { - component.registrationData = Object.assign(new RegistrationData(), registrationDataMock, { email: 'email@domain.com' }); - fixture.detectChanges(); - const selector = compiledTemplate.querySelector('ds-confirm-email'); - expect(selector).toBeTruthy(); - }); - - it('should display provide email component if email is not provided', () => { - component.registrationData.email = null; - fixture.detectChanges(); + const selector = fixture.nativeElement.querySelector('ds-confirm-email'); const provideEmail = fixture.nativeElement.querySelector('ds-provide-email'); - expect(provideEmail).toBeTruthy(); + expect(selector).toBeTruthy(); + expect(provideEmail).toBeNull(); }); it('should display login modal when connect to existing account button is clicked', () => { - const button = fixture.nativeElement.querySelector('button'); + const button = fixture.nativeElement.querySelector('button.btn-primary'); button.click(); - fixture.detectChanges(); - const modal = fixture.nativeElement.querySelector('.modal'); - expect(modal).toBeTruthy(); + expect(modalService.open).toHaveBeenCalled(); }); it('should render the template with the translated informationText', () => { diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts index 24d40f11533..a5ad2df9d43 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts @@ -1,16 +1,47 @@ import { TestBed } from '@angular/core/testing'; import { RegistrationDataResolver } from './registration-data.resolver'; +import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; +import { Registration } from 'src/app/core/shared/registration.model'; +import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { RegistrationData } from '../models/registration-data.model'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; describe('RegistrationDataResolver', () => { let resolver: RegistrationDataResolver; + let epersonRegistrationServiceSpy: jasmine.SpyObj; + const registrationMock = Object.assign(new Registration(), { + email: 'test@user.com', + }); beforeEach(() => { - TestBed.configureTestingModule({}); + const spy = jasmine.createSpyObj('EpersonRegistrationService', ['searchByTokenAndUpdateData']); + + TestBed.configureTestingModule({ + providers: [ + RegistrationDataResolver, + { provide: EpersonRegistrationService, useValue: spy } + ] + }); resolver = TestBed.inject(RegistrationDataResolver); + epersonRegistrationServiceSpy = TestBed.inject(EpersonRegistrationService) as jasmine.SpyObj; }); it('should be created', () => { expect(resolver).toBeTruthy(); }); + + it('should resolve registration data based on a token', () => { + const token = 'abc123'; + const registrationRD$ = createSuccessfulRemoteDataObject$(registrationMock); + epersonRegistrationServiceSpy.searchByTokenAndUpdateData.and.returnValue(registrationRD$); + + const route = new ActivatedRouteSnapshot(); + route.params = { token: token }; + const state = {} as RouterStateSnapshot; + + resolver.resolve(route, state).subscribe((result: RegistrationData) => { + expect(result).toBeDefined(); + }); + }); }); diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts index 7d1d8ac6a2c..541e71e6a5a 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts @@ -1,16 +1,76 @@ import { TestBed } from '@angular/core/testing'; import { ExternalLoginService } from './external-login.service'; +import { TranslateService } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Registration } from '../../../core/shared/registration.model'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { RouterMock } from '../../mocks/router.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Router } from '@angular/router'; describe('ExternalLoginService', () => { let service: ExternalLoginService; + let epersonRegistrationService; + let router: any; + let notificationService; + let translate; + + const values = ['value1', 'value2']; + const field = 'field1'; + const registrationId = 'registrationId1'; + const token = 'token1'; + const operation = 'add'; beforeEach(() => { - TestBed.configureTestingModule({}); + epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { + patchUpdateRegistration: createSuccessfulRemoteDataObject$(new Registration) + }); + router = new RouterMock(); + notificationService = new NotificationsServiceStub(); + translate = jasmine.createSpyObj('TranslateService', ['get']); + + TestBed.configureTestingModule({ + providers: [ + ExternalLoginService, + { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, + { provide: Router, useValue: router }, + { provide: NotificationsService, useValue: notificationService }, + { provide: TranslateService, useValue: translate }, + ], + schemas: [NO_ERRORS_SCHEMA] + }); service = TestBed.inject(ExternalLoginService); }); it('should be created', () => { expect(service).toBeTruthy(); }); + + it('should call epersonRegistrationService.patchUpdateRegistration with the correct parameters', () => { + epersonRegistrationService.patchUpdateRegistration.and.returnValue(observableOf({} as RemoteData)); + service.patchUpdateRegistration(values, field, registrationId, token, operation); + expect(epersonRegistrationService.patchUpdateRegistration).toHaveBeenCalledWith(values, field, registrationId, token, operation); + }); + + it('should navigate to /email-confirmation if the remote data has succeeded', () => { + const remoteData = { hasSucceeded: true } as RemoteData; + epersonRegistrationService.patchUpdateRegistration.and.returnValue(observableOf(remoteData)); + service.patchUpdateRegistration(values, field, registrationId, token, operation).subscribe(() => { + expect((router as any).navigate).toHaveBeenCalledWith(['/email-confirmation']); + }); + }); + + it('should show an error notification if the remote data has failed', () => { + const remoteData = { hasFailed: true } as RemoteData; + epersonRegistrationService.patchUpdateRegistration.and.returnValue(observableOf(remoteData)); + translate.get.and.returnValue(observableOf('error message')); + service.patchUpdateRegistration(values, field, registrationId, token, operation).subscribe(() => { + expect(notificationService.error).toHaveBeenCalledWith('error message'); + }); + }); }); From 742a078306ff5453a9402ab956d7d10f47176c0f Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 11:22:56 +0200 Subject: [PATCH 25/56] [CST-10703] RegistrationTokenGuard fix --- .../guards/registration-token.guard.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts index 3d4d103b098..0a4a1319344 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts @@ -19,7 +19,7 @@ export class RegistrationTokenGuard implements CanActivate { constructor( private router: Router, private epersonRegistrationService: EpersonRegistrationService - ) {} + ) { } /** * Determines if a user can activate a route based on the registration token. @@ -31,20 +31,22 @@ export class RegistrationTokenGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Promise | boolean | Observable { - if (route.params.token) { + if (route.queryParams.token) { return this.epersonRegistrationService .searchRegistrationByToken(route.params.token) .pipe( getFirstCompletedRemoteData(), map( - (data: RemoteData) => - data.hasSucceeded && hasValue(data) - ), - tap((isValid: boolean) => { - if (!isValid) { - this.router.navigate(['/404']); + (data: RemoteData) => { + // TODO: remove console.log + console.log(data, 'RegistrationTokenGuard'); + if (data.hasSucceeded && hasValue(data)) { + return true; + } else { + this.router.navigate(['/404']); + } } - }) + ) ); } else { this.router.navigate(['/404']); From ae875a860e55d16fcf13b864795386624afa3999 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 11:26:25 +0200 Subject: [PATCH 26/56] [CST-10703] fix --- .../external-log-in-complete/guards/registration-token.guard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts index 0a4a1319344..1296385602a 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts @@ -33,7 +33,7 @@ export class RegistrationTokenGuard implements CanActivate { ): Promise | boolean | Observable { if (route.queryParams.token) { return this.epersonRegistrationService - .searchRegistrationByToken(route.params.token) + .searchRegistrationByToken(route.queryParams.token) .pipe( getFirstCompletedRemoteData(), map( From 03ea663604414a697d11acc256b15e52b7152fb5 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 11:28:59 +0200 Subject: [PATCH 27/56] [CST-10703] small fixes --- .../helpers/review-account.guard.ts | 4 ++-- .../resolvers/registration-data.resolver.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.ts index c979e3bfc2c..5a1eb8b1c2d 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.ts @@ -36,9 +36,9 @@ export class ReviewAccountGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Promise | boolean | Observable { - if (route.params.token) { + if (route.queryParams.token) { return this.epersonRegistrationService - .searchRegistrationByToken(route.params.token) + .searchRegistrationByToken(route.queryParams.token) .pipe( getFirstCompletedRemoteData(), mergeMap( diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts index 6e2a9dc9fc8..82d0bdc68b7 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts @@ -33,7 +33,7 @@ export class RegistrationDataResolver implements Resolve { * @returns An Observable of RegistrationData. */ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - const token = route.params.token; + const token = route.queryParams.token; if (hasValue(token)) { return this.epersonRegistrationService.searchByTokenAndUpdateData(token).pipe( getFirstCompletedRemoteData(), From 9518e3d267296e921874123c67640e1537ce7467 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 12:46:50 +0200 Subject: [PATCH 28/56] [CST-10703] extend Registration model --- src/app/core/auth/models/auth.method-type.ts | 1 - .../auth/models/auth.registration-type.ts | 7 ++ src/app/core/core.module.ts | 2 - src/app/core/shared/registration.model.ts | 31 +++++++ .../external-login-page.component.spec.ts | 4 +- .../external-login-page.component.ts | 9 ++- ...ogin-review-account-info-page.component.ts | 13 +-- .../helpers/review-account.guard.spec.ts | 10 +-- .../helpers/review-account.guard.ts | 20 +++-- .../review-account-info.component.spec.ts | 4 +- .../review-account-info.component.ts | 14 ++-- .../confirm-email.component.spec.ts | 18 ++--- .../confirm-email/confirm-email.component.ts | 6 +- .../external-log-in.methods-decorator.ts | 6 +- .../external-log-in.component.spec.ts | 10 +-- .../external-log-in.component.ts | 8 +- .../external-login-method-entry.component.ts | 6 +- .../guards/registration-token.guard.ts | 4 +- .../models/registration-data.mock.model.ts | 8 +- .../models/registration-data.model.ts | 80 ------------------- .../models/registration-data.resource-type.ts | 9 --- .../orcid-confirmation.component.ts | 10 +-- .../registration-data.resolver.spec.ts | 7 +- .../resolvers/registration-data.resolver.ts | 26 +++--- 24 files changed, 126 insertions(+), 187 deletions(-) create mode 100644 src/app/core/auth/models/auth.registration-type.ts delete mode 100644 src/app/shared/external-log-in-complete/models/registration-data.model.ts delete mode 100644 src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts diff --git a/src/app/core/auth/models/auth.method-type.ts b/src/app/core/auth/models/auth.method-type.ts index e5b4e0a194c..cc19b5ab94c 100644 --- a/src/app/core/auth/models/auth.method-type.ts +++ b/src/app/core/auth/models/auth.method-type.ts @@ -6,5 +6,4 @@ export enum AuthMethodType { X509 = 'x509', Oidc = 'oidc', Orcid = 'orcid', - Validation = 'validation', } diff --git a/src/app/core/auth/models/auth.registration-type.ts b/src/app/core/auth/models/auth.registration-type.ts new file mode 100644 index 00000000000..e43094ed7bc --- /dev/null +++ b/src/app/core/auth/models/auth.registration-type.ts @@ -0,0 +1,7 @@ +export enum AuthRegistrationType { + Password = 'password', + Shibboleth = 'shibboleth', + Oidc = 'oidc', + Orcid = 'ORCID', + Validation = 'validation', +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index c75ce8e65c9..1c6b20285a8 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -232,7 +232,6 @@ import { import { ProductDatasetSchemaType } from './metadata/schema-json-ld/schema-types/product/product-dataset-schema-type'; import { PersonSchemaType } from './metadata/schema-json-ld/schema-types/Person/person-schema-type'; import {ItemRequest} from './shared/item-request.model'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -472,7 +471,6 @@ export const models = LoginStatistics, Metric, ItemRequest, - RegistrationData, ]; @NgModule({ diff --git a/src/app/core/shared/registration.model.ts b/src/app/core/shared/registration.model.ts index bc4488964f2..5a3a5d06563 100644 --- a/src/app/core/shared/registration.model.ts +++ b/src/app/core/shared/registration.model.ts @@ -1,11 +1,26 @@ +// eslint-disable-next-line max-classes-per-file import { typedObject } from '../cache/builders/build-decorators'; import { ResourceType } from './resource-type'; import { REGISTRATION } from './registration.resource-type'; import { UnCacheableObject } from './uncacheable-object.model'; +import { MetadataValue } from './metadata.models'; +import { AuthRegistrationType } from '../auth/models/auth.registration-type'; +export class RegistrationDataMetadataMap { + [key: string]: RegistrationDataMetadataValue[]; +} +export class RegistrationDataMetadataValue extends MetadataValue { + overrides?: string; +} @typedObject export class Registration implements UnCacheableObject { static type = REGISTRATION; + + /** + * The unique identifier of this registration data + */ + id: string; + /** * The object type */ @@ -29,8 +44,24 @@ export class Registration implements UnCacheableObject { * The token linked to the registration */ groupNames: string[]; + /** * The token linked to the registration */ groups: string[]; + + /** + * The registration type (e.g. orcid, shibboleth, etc.) + */ + registrationType?: AuthRegistrationType; + + /** + * The netId of the user (e.g. for ORCID - <:orcid>) + */ + netId?: string; + + /** + * The metadata involved during the registration process + */ + registrationMetadata?: RegistrationDataMetadataMap; } diff --git a/src/app/external-login-page/external-login-page.component.spec.ts b/src/app/external-login-page/external-login-page.component.spec.ts index c705b131b3e..8dee4821a7a 100644 --- a/src/app/external-login-page/external-login-page.component.spec.ts +++ b/src/app/external-login-page/external-login-page.component.spec.ts @@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginPageComponent } from './external-login-page.component'; import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { CommonModule } from '@angular/common'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; +import { Registration } from '../core/shared/registration.model'; describe('ExternalLoginPageComponent', () => { let component: ExternalLoginPageComponent; @@ -75,7 +75,7 @@ describe('ExternalLoginPageComponent', () => { }); it('should display the DsExternalLogIn component when there are no errors', () => { - const registrationData = Object.assign(new RegistrationData(), registrationDataMock); + const registrationData = Object.assign(new Registration(), registrationDataMock); component.registrationData$ = of(registrationData); component.token = '1234567890'; component.hasErrors = false; diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index 27e08e4e5ae..1e3862bcd2f 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { hasNoValue } from '../shared/empty.util'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { AlertType } from '../shared/alert/aletr-type'; import { Observable, first, map, tap } from 'rxjs'; +import { Registration } from '../core/shared/registration.model'; +import { RemoteData } from '../core/data/remote-data'; @Component({ templateUrl: './external-login-page.component.html', @@ -19,7 +20,7 @@ export class ExternalLoginPageComponent implements OnInit { /** * The registration data of the user. */ - public registrationData$: Observable; + public registrationData$: Observable; /** * The type of alert to show. */ @@ -39,7 +40,7 @@ export class ExternalLoginPageComponent implements OnInit { ngOnInit(): void { this.registrationData$ = this.arouter.data.pipe( first(), - tap((data) => this.hasErrors = hasNoValue(data.registrationData)), - map((data) => data.registrationData)); + tap((data) => this.hasErrors = (data.registrationData as RemoteData).hasFailed), + map((data) => (data.registrationData as RemoteData).payload)); } } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts index 6e0cab84594..dedb3d9baa1 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { AlertType } from '../shared/alert/aletr-type'; import { Observable, first, map, tap } from 'rxjs'; -import { RegistrationData } from '../shared/external-log-in-complete/models/registration-data.model'; import { ActivatedRoute } from '@angular/router'; import { hasNoValue } from '../shared/empty.util'; +import { Registration } from '../core/shared/registration.model'; +import { RemoteData } from '../core/data/remote-data'; @Component({ templateUrl: './external-login-review-account-info-page.component.html', @@ -23,7 +24,7 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { /** * The registration data of the user */ - public registrationData$: Observable; + public registrationData$: Observable; /** * Whether the component has errors */ @@ -36,8 +37,10 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { } ngOnInit(): void { - this.registrationData$ = this.arouter.data.pipe(first(), - tap((data) => this.hasErrors = hasNoValue(data?.registrationData)), - map((data) => data.registrationData)); + this.registrationData$ = this.arouter.data.pipe( + first(), + tap((data) => this.hasErrors = (data.registrationData as RemoteData).hasFailed), + map((data) => (data.registrationData as RemoteData).payload)); } } + diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts index 03e8d53343c..9004e173204 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts @@ -6,8 +6,8 @@ import { AuthService } from '../../core/auth/auth.service'; import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; import { RouterMock } from '../../shared/mocks/router.mock'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; -import { AuthMethodType } from '../../core/auth/models/auth.method-type'; +import { Registration } from '../../core/shared/registration.model'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; describe('ReviewAccountGuard', () => { let guard: ReviewAccountGuard; @@ -15,10 +15,10 @@ describe('ReviewAccountGuard', () => { let authService: any; const route = new RouterMock(); - const registrationMock = Object.assign(new RegistrationData(), + const registrationMock = Object.assign(new Registration(), { email: 'test@email.org', - registrationType: AuthMethodType.Validation + registrationType: AuthRegistrationType.Validation }); @@ -68,7 +68,7 @@ describe('ReviewAccountGuard', () => { }); it('should navigate to 404 if the registration type is not validation and the user is not authenticated', () => { - registrationMock.registrationType = AuthMethodType.Password; + registrationMock.registrationType = AuthRegistrationType.Password; epersonRegistrationService.searchRegistrationByToken.and.returnValue(createSuccessfulRemoteDataObject$(registrationMock)); spyOn(authService, 'isAuthenticated').and.returnValue(of(false)); (guard.canActivate({ params: { token: 'invalid-token' } } as any, {} as any) as any).subscribe((result) => { diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.ts index 5a1eb8b1c2d..8ae1bca6f25 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.ts @@ -7,14 +7,13 @@ import { } from '@angular/router'; import isEqual from 'lodash/isEqual'; import { Observable, catchError, mergeMap, of, tap } from 'rxjs'; -import { AuthService } from 'src/app/core/auth/auth.service'; -import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; -import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; -import { RemoteData } from 'src/app/core/data/remote-data'; -import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; -import { Registration } from 'src/app/core/shared/registration.model'; -import { hasValue } from 'src/app/shared/empty.util'; -import { RegistrationData } from 'src/app/shared/external-log-in-complete/models/registration-data.model'; +import { AuthService } from '../../core/auth/auth.service'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Registration } from '../../core/shared/registration.model'; +import { hasValue } from '../../shared/empty.util'; @Injectable({ providedIn: 'root', @@ -43,10 +42,9 @@ export class ReviewAccountGuard implements CanActivate { getFirstCompletedRemoteData(), mergeMap( (data: RemoteData) => { - if (data.hasSucceeded && hasValue(data)) { - const registrationData = Object.assign(new RegistrationData(), data.payload); + if (data.hasSucceeded && hasValue(data.payload)) { // is the registration type validation (account valid) - if (isEqual(registrationData.registrationType, AuthMethodType.Validation)) { + if (hasValue(data.payload.registrationType) && isEqual(data.payload.registrationType, AuthRegistrationType.Validation)) { return of(true); } else { return this.authService.isAuthenticated(); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index 70185c37543..b7663674972 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -19,9 +19,9 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { Router } from '@angular/router'; import { RouterMock } from '../../shared/mocks/router.mock'; -import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; import { EventEmitter } from '@angular/core'; import { CompareValuesPipe } from '../helpers/compare-values.pipe'; +import { Registration } from '../../core/shared/registration.model'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; @@ -96,7 +96,7 @@ describe('ReviewAccountInfoComponent', () => { fixture = TestBed.createComponent(ReviewAccountInfoComponent); component = fixture.componentInstance; component.registrationData = Object.assign( - new RegistrationData(), + new Registration(), registrationDataMock ); component.registrationToken = 'test-token'; diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index ea6b341710e..81c237b7fc2 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -7,15 +7,15 @@ import { } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { RegistrationData } from '../../shared/external-log-in-complete/models/registration-data.model'; import { Observable, Subscription, filter, from, switchMap, take } from 'rxjs'; -import { RemoteData } from '../..//core/data/remote-data'; -import { ConfirmationModalComponent } from '../..//shared/confirmation-modal/confirmation-modal.component'; +import { RemoteData } from '../../core/data/remote-data'; +import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { hasValue } from '../..//shared/empty.util'; +import { hasValue } from '../../shared/empty.util'; import { TranslateService } from '@ngx-translate/core'; -import { NotificationsService } from '../..//shared/notifications/notifications.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; import { Router } from '@angular/router'; +import { Registration } from '../../core/shared/registration.model'; export interface ReviewAccountInfoData { label: string; @@ -39,7 +39,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { /** * User data from the registration token */ - @Input() registrationData: RegistrationData; + @Input() registrationData: Registration; /** * Text to display when the value is not applicable @@ -143,7 +143,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { } if (response.hasFailed) { - this.notificationService.success( + this.notificationService.error( this.translateService.get( 'review-account-info.merge-data.notification.error' ) diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index 1a43180bbc0..c9c741672dd 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -7,16 +7,16 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; -import { AuthService } from 'src/app/core/auth/auth.service'; -import { EPersonDataService } from 'src/app/core/eperson/eperson-data.service'; -import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; -import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; -import { RegistrationData } from '../../models/registration-data.model'; -import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; -import { EPerson } from 'src/app/core/eperson/models/eperson.model'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Router } from '@angular/router'; -import { RouterMock } from 'src/app/shared/mocks/router.mock'; +import { RouterMock } from '../../../../shared/mocks/router.mock'; import { of } from 'rxjs'; +import { Registration } from '../../../../core/shared/registration.model'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; @@ -72,7 +72,7 @@ describe('ConfirmEmailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ConfirmEmailComponent); component = fixture.componentInstance; - component.registrationData = Object.assign(new RegistrationData(), { + component.registrationData = Object.assign(new Registration(), { id: '123', email: 'test@example.com', registrationMetadata: {}, diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 449870f420b..ba5c3d3d138 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -2,7 +2,6 @@ import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/c import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../core/shared/operators'; -import { RegistrationData } from '../../models/registration-data.model'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { hasValue } from '../../../../shared/empty.util'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; @@ -13,6 +12,7 @@ import isEqual from 'lodash/isEqual'; import { AuthService } from '../../../../core/auth/auth.service'; import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; +import { Registration } from '../../../../core/shared/registration.model'; @Component({ selector: 'ds-confirm-email', @@ -28,7 +28,7 @@ export class ConfirmEmailComponent implements OnDestroy { /** * The registration data object */ - @Input() registrationData: RegistrationData; + @Input() registrationData: Registration; /** * The token to be used to confirm the registration @@ -99,7 +99,7 @@ export class ConfirmEmailComponent implements OnDestroy { */ private postCreateAccountFromToken( token: string, - registrationData: RegistrationData + registrationData: Registration ) { const metadataValues = {}; for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { diff --git a/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts b/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts index 933edf24640..ce90aea0a3f 100644 --- a/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts +++ b/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts @@ -1,4 +1,4 @@ -import { AuthMethodType } from '../../core/auth/models/auth.method-type'; +import { AuthRegistrationType } from 'src/app/core/auth/models/auth.registration-type'; /** * Map to store the external login confirmation component for the given auth method type @@ -9,7 +9,7 @@ const authMethodsMap = new Map(); * @param authMethodType the type of the external login method */ export function renderExternalLoginConfirmationFor( - authMethodType: AuthMethodType + authMethodType: AuthRegistrationType ) { return function decorator(objectElement: any) { if (!objectElement) { @@ -23,7 +23,7 @@ export function renderExternalLoginConfirmationFor( * @param authMethodType the type of the external login method */ export function getExternalLoginConfirmationType( - authMethodType: AuthMethodType + authMethodType: AuthRegistrationType ) { return authMethodsMap.get(authMethodType); } diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts index f34a7883d1b..9e3b74f2bfe 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts @@ -12,8 +12,8 @@ import { AuthService } from '../../../core/auth/auth.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AuthServiceMock } from '../../mocks/auth.service.mock'; import { MetadataValue } from '../../../core/shared/metadata.models'; -import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; -import { RegistrationData } from '../models/registration-data.model'; +import { Registration } from '../../../core/shared/registration.model'; +import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; describe('ExternalLogInComponent', () => { let component: ExternalLogInComponent; @@ -24,7 +24,7 @@ describe('ExternalLogInComponent', () => { id: '3', email: 'user@institution.edu', user: '028dcbb8-0da2-4122-a0ea-254be49ca107', - registrationType: AuthMethodType.Orcid, + registrationType: AuthRegistrationType.Orcid, netId: '0000-1111-2222-3333', registrationMetadata: { 'eperson.firstname': [ @@ -72,7 +72,7 @@ describe('ExternalLogInComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ExternalLogInComponent); component = fixture.componentInstance; - component.registrationData = Object.assign(new RegistrationData, registrationDataMock); + component.registrationData = Object.assign(new Registration, registrationDataMock); component.registrationType = registrationDataMock.registrationType; fixture.detectChanges(); }); @@ -82,7 +82,7 @@ describe('ExternalLogInComponent', () => { }); beforeEach(() => { - component.registrationData = Object.assign(new RegistrationData(), registrationDataMock, { email: 'user@institution.edu' }); + component.registrationData = Object.assign(new Registration(), registrationDataMock, { email: 'user@institution.edu' }); fixture.detectChanges(); }); diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts index 4e08d633a21..0a077405429 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -6,12 +6,12 @@ import { Injector, } from '@angular/core'; import { getExternalLoginConfirmationType } from '../external-log-in.methods-decorator'; -import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; -import { RegistrationData } from '../models/registration-data.model'; import { hasValue } from '../../empty.util'; import { TranslateService } from '@ngx-translate/core'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { AuthService } from '../../../core/auth/auth.service'; +import { Registration } from '../../../core/shared/registration.model'; +import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; @Component({ selector: 'ds-external-log-in', @@ -23,11 +23,11 @@ export class ExternalLogInComponent implements OnInit { /** * The type of registration type to be confirmed */ - registrationType: AuthMethodType; + registrationType: AuthRegistrationType; /** * The registration data object */ - @Input() registrationData: RegistrationData; + @Input() registrationData: Registration; /** * The token to be used to confirm the registration * @memberof ExternalLogInComponent diff --git a/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts b/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts index 5d03342e0f4..47158274b86 100644 --- a/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts +++ b/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts @@ -1,5 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { RegistrationData } from './models/registration-data.model'; +import { Registration } from '../../core/shared/registration.model'; /** * This component renders a form to complete the registration process @@ -12,10 +12,10 @@ export abstract class ExternalLoginMethodEntryComponent { /** * The registration data object */ - public registratioData: RegistrationData; + public registratioData: Registration; constructor( - @Inject('registrationDataProvider') protected injectedRegistrationDataObject: RegistrationData, + @Inject('registrationDataProvider') protected injectedRegistrationDataObject: Registration, ) { this.registratioData = injectedRegistrationDataObject; } diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts index 1296385602a..ec37d9d44e7 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts @@ -5,7 +5,7 @@ import { Router, RouterStateSnapshot, } from '@angular/router'; -import { Observable, map, of, tap } from 'rxjs'; +import { Observable, map, of } from 'rxjs'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; @@ -30,7 +30,7 @@ export class RegistrationTokenGuard implements CanActivate { canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot - ): Promise | boolean | Observable { + ): Observable { if (route.queryParams.token) { return this.epersonRegistrationService .searchRegistrationByToken(route.queryParams.token) diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts index fa40cb91c3b..6dc1eb28632 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts @@ -1,9 +1,9 @@ -import { AuthMethodType } from 'src/app/core/auth/models/auth.method-type'; -import { RegistrationData } from './registration-data.model'; +import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; import { MetadataValue } from '../../../core/shared/metadata.models'; +import { Registration } from '../../../core/shared/registration.model'; -export const mockRegistrationDataModel: RegistrationData = Object.assign( - new RegistrationData(), +export const mockRegistrationDataModel: Registration = Object.assign( + new Registration(), { id: '3', email: 'user@institution.edu', diff --git a/src/app/shared/external-log-in-complete/models/registration-data.model.ts b/src/app/shared/external-log-in-complete/models/registration-data.model.ts deleted file mode 100644 index 9714d81111b..00000000000 --- a/src/app/shared/external-log-in-complete/models/registration-data.model.ts +++ /dev/null @@ -1,80 +0,0 @@ -// eslint-disable-next-line max-classes-per-file -import { CacheableObject } from '../../../core/cache/cacheable-object.model'; -import { typedObject } from '../../../core/cache/builders/build-decorators'; -import { REGISTRATION_DATA } from './registration-data.resource-type'; -import { autoserialize, deserialize } from 'cerialize'; -import { excludeFromEquals } from '../../../core/utilities/equals.decorators'; -import { ResourceType } from '../../../core/shared/resource-type'; -import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; -import { HALLink } from '../../../core/shared/hal-link.model'; -import { MetadataValue } from '../../../core/shared/metadata.models'; - -export class RegistrationDataMetadataMap { - [key: string]: RegistrationDataMetadataValue[]; -} - -export class RegistrationDataMetadataValue extends MetadataValue { - @autoserialize - overrides?: string; -} - -/** - * Object that represents the authenticated status of a user - */ -@typedObject -export class RegistrationData implements CacheableObject { - - static type = REGISTRATION_DATA; - - /** - * The unique identifier of this registration data - */ - @autoserialize - id: string; - - /** - * The type for this RegistrationData - */ - @excludeFromEquals - @autoserialize - type: ResourceType; - - /** - * The registered email address - */ - @autoserialize - email: string; - - /** - * The registered user identifier - */ - @autoserialize - user: string; - - /** - * The registration type (e.g. orcid, shibboleth, etc.) - */ - @autoserialize - registrationType?: AuthMethodType; - - /** - * The netId of the user (e.g. for ORCID - <:orcid>) - */ - @autoserialize - netId?: string; - - - /** - * The metadata involved during the registration process - */ - @autoserialize - registrationMetadata?: RegistrationDataMetadataMap; - - /** - * The {@link HALLink}s for this RegistrationData - */ - @deserialize - _links: { - self: HALLink; - }; -} diff --git a/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts b/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts deleted file mode 100644 index 2a387c3ca38..00000000000 --- a/src/app/shared/external-log-in-complete/models/registration-data.resource-type.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ResourceType } from '../../../core/shared/resource-type'; - -/** - * The resource type for RegistrationData - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const REGISTRATION_DATA = new ResourceType('registration'); diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts index 0c93b668ec7..3cd3b875dc0 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, ChangeDetectionStrategy, Inject } from '@angular/core'; import { renderExternalLoginConfirmationFor } from '../../external-log-in.methods-decorator'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { RegistrationData } from '../../models/registration-data.model'; import { ExternalLoginMethodEntryComponent } from '../../external-login-method-entry.component'; +import { Registration } from '../../../../core/shared/registration.model'; +import { AuthRegistrationType } from '../../../../core/auth/models/auth.registration-type'; @Component({ selector: 'ds-orcid-confirmation', @@ -11,7 +11,7 @@ import { ExternalLoginMethodEntryComponent } from '../../external-login-method-e styleUrls: ['./orcid-confirmation.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -@renderExternalLoginConfirmationFor(AuthMethodType.Orcid) +@renderExternalLoginConfirmationFor(AuthRegistrationType.Orcid) export class OrcidConfirmationComponent extends ExternalLoginMethodEntryComponent implements OnInit { /** @@ -20,11 +20,11 @@ export class OrcidConfirmationComponent extends ExternalLoginMethodEntryComponen public form: FormGroup; /** - * @param injectedRegistrationDataObject RegistrationData object provided + * @param injectedRegistrationDataObject Registration object provided * @param formBuilder To build the form */ constructor( - @Inject('registrationDataProvider') protected injectedRegistrationDataObject: RegistrationData, + @Inject('registrationDataProvider') protected injectedRegistrationDataObject: Registration, private formBuilder: FormBuilder ) { super(injectedRegistrationDataObject); diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts index a5ad2df9d43..a33434062b5 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts @@ -1,10 +1,9 @@ import { TestBed } from '@angular/core/testing'; import { RegistrationDataResolver } from './registration-data.resolver'; -import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; -import { Registration } from 'src/app/core/shared/registration.model'; +import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import { Registration } from '../../../core/shared/registration.model'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { RegistrationData } from '../models/registration-data.model'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; describe('RegistrationDataResolver', () => { @@ -40,7 +39,7 @@ describe('RegistrationDataResolver', () => { route.params = { token: token }; const state = {} as RouterStateSnapshot; - resolver.resolve(route, state).subscribe((result: RegistrationData) => { + resolver.resolve(route, state).subscribe((result: Registration) => { expect(result).toBeDefined(); }); }); diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts index 82d0bdc68b7..5513a847a43 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts @@ -4,13 +4,12 @@ import { RouterStateSnapshot, ActivatedRouteSnapshot, } from '@angular/router'; -import { Observable, map } from 'rxjs'; -import { EpersonRegistrationService } from 'src/app/core/data/eperson-registration.service'; +import { Observable } from 'rxjs'; +import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { hasValue } from '../../empty.util'; -import { Registration } from 'src/app/core/shared/registration.model'; -import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; -import { RemoteData } from 'src/app/core/data/remote-data'; -import { RegistrationData } from '../models/registration-data.model'; +import { Registration } from '../../../core/shared/registration.model'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { RemoteData } from '../../../core/data/remote-data'; @Injectable({ providedIn: 'root', @@ -18,7 +17,7 @@ import { RegistrationData } from '../models/registration-data.model'; /** * Resolver for retrieving registration data based on a token. */ -export class RegistrationDataResolver implements Resolve { +export class RegistrationDataResolver implements Resolve> { /** * Constructor for RegistrationDataResolver. @@ -30,20 +29,13 @@ export class RegistrationDataResolver implements Resolve { * Resolves registration data based on a token. * @param route The ActivatedRouteSnapshot containing the token parameter. * @param state The RouterStateSnapshot. - * @returns An Observable of RegistrationData. + * @returns An Observable of Registration. */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { const token = route.queryParams.token; if (hasValue(token)) { - return this.epersonRegistrationService.searchByTokenAndUpdateData(token).pipe( + return this.epersonRegistrationService.searchRegistrationByToken(token).pipe( getFirstCompletedRemoteData(), - map((registrationRD: RemoteData) => { - if (registrationRD.hasSucceeded && hasValue(registrationRD.payload)) { - return Object.assign(new RegistrationData(), registrationRD.payload); - } else { - return null; - } - }) ); } } From b62aaad77a426b67cf4977540a33b7fb897b0337 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 14:39:01 +0200 Subject: [PATCH 29/56] [CST-10703] confirm email fixes --- src/app/core/data/eperson-registration.service.ts | 3 ++- .../confirm-email/confirm-email.component.ts | 9 ++++----- .../provide-email/provide-email.component.ts | 5 +---- .../services/external-login.service.ts | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 259b5f66c50..43bd957f928 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -16,6 +16,7 @@ import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { HttpHeaders } from '@angular/common/http'; import { HttpParams } from '@angular/common/http'; import { Operation } from 'fast-json-patch'; +import { NoContent } from '../shared/NoContent.model'; @Injectable({ providedIn: 'root', }) @@ -157,7 +158,7 @@ export class EpersonRegistrationService{ * @param updateValue Flag to indicate if the email should be updated or added * @returns Remote Data state of the patch request */ - patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operator: 'add' | 'replace'): Observable> { + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operator: 'add' | 'replace'): Observable> { const requestId = this.requestService.generateRequestId(); const href$ = this.getRegistrationEndpoint().pipe( diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index ba5c3d3d138..696d5d265e1 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -80,10 +80,7 @@ export class ConfirmEmailComponent implements OnDestroy { this.subs.push( this.externalLoginService.patchUpdateRegistration(values, 'email', this.registrationData.id, this.token, 'replace') .pipe(getRemoteDataPayload()) - .subscribe((update) => { - // TODO: remove this line (temporary) - console.log('Email update:', update); - })); + .subscribe()); } /** @@ -104,14 +101,16 @@ export class ConfirmEmailComponent implements OnDestroy { const metadataValues = {}; for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { if (hasValue(value[0]?.value) && key !== 'email') { - metadataValues[key] = value[0]; + metadataValues[key] = value; } } const eperson = new EPerson(); eperson.email = registrationData.email; + eperson.netid = registrationData.netId; eperson.metadata = metadataValues; eperson.canLogIn = true; eperson.requireCertificate = false; + eperson.selfRegistered = true; this.subs.push( this.epersonDataService.createEPersonForToken(eperson, token).pipe( getFirstCompletedRemoteData(), diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index 92faf10e756..5efbd0f7d62 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -48,10 +48,7 @@ export class ProvideEmailComponent implements OnDestroy { if (this.emailForm.valid) { const email = this.emailForm.get('email').value; this.subs.push(this.externalLoginService.patchUpdateRegistration([email], 'email', this.registrationId, this.token, 'add') - .subscribe((rd: RemoteData) => { - // TODO: remove this line (temporary) - console.log('Email update:', rd); - })); + .subscribe()); } } diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index 1ec730516c4..3d3f97571c9 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -6,7 +6,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { NotificationsService } from '../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { Registration } from 'src/app/core/shared/registration.model'; +import { NoContent } from '../../../core/shared/NoContent.model'; @Injectable({ providedIn: 'root' @@ -29,7 +29,7 @@ export class ExternalLoginService { * @param token the registration token * @param operation operation to be performed */ - patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable>{ + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable>{ const updatedValues = values.map((value) => value); return this.epersonRegistrationService.patchUpdateRegistration(updatedValues, field, registrationId, token, operation).pipe( getFirstCompletedRemoteData(), From 236338f5e665932842843d5339609dbe460aeffe Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 15:04:23 +0200 Subject: [PATCH 30/56] [CST-10703] changed how to get token parameter --- src/app/app-routing.module.ts | 4 ++-- .../external-login-page/external-login-page.component.ts | 4 ++-- .../external-login-review-account-info-page.component.ts | 3 ++- .../helpers/review-account.guard.ts | 4 ++-- .../provide-email/provide-email.component.ts | 2 -- .../guards/registration-token.guard.ts | 6 ++---- .../resolvers/registration-data.resolver.ts | 2 +- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index fb4d658d8cb..a652eadde9e 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -172,12 +172,12 @@ import { RedirectService } from './redirect/redirect.service'; .then((m) => m.LoginPageModule) }, { - path: 'external-login', + path: 'external-login/:token', loadChildren: () => import('./external-login-page/external-login-page.module') .then((m) => m.ExternalLoginPageModule) }, { - path: 'review-account', + path: 'review-account/:token', loadChildren: () => import('./external-login-review-account-info/external-login-review-account-info-page.module') .then((m) => m.ExternalLoginReviewAccountInfoModule) }, diff --git a/src/app/external-login-page/external-login-page.component.ts b/src/app/external-login-page/external-login-page.component.ts index 1e3862bcd2f..d717bce739b 100644 --- a/src/app/external-login-page/external-login-page.component.ts +++ b/src/app/external-login-page/external-login-page.component.ts @@ -33,8 +33,8 @@ export class ExternalLoginPageComponent implements OnInit { constructor( private arouter: ActivatedRoute ) { - this.token = this.arouter.snapshot.queryParams.token; - this.hasErrors = hasNoValue(this.arouter.snapshot.queryParams.token); + this.token = this.arouter.snapshot.params.token; + this.hasErrors = hasNoValue(this.arouter.snapshot.params.token); } ngOnInit(): void { diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts index dedb3d9baa1..a42c9e05cfd 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts @@ -33,7 +33,8 @@ export class ExternalLoginReviewAccountInfoPageComponent implements OnInit { constructor( private arouter: ActivatedRoute ) { - this.token = this.arouter.snapshot.queryParams.token; + this.token = this.arouter.snapshot.params.token; + this.hasErrors = hasNoValue(this.arouter.snapshot.params.token); } ngOnInit(): void { diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.ts index 8ae1bca6f25..03caf6d63ed 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.ts @@ -35,9 +35,9 @@ export class ReviewAccountGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Promise | boolean | Observable { - if (route.queryParams.token) { + if (route.params.token) { return this.epersonRegistrationService - .searchRegistrationByToken(route.queryParams.token) + .searchRegistrationByToken(route.params.token) .pipe( getFirstCompletedRemoteData(), mergeMap( diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index 5efbd0f7d62..a63486dea60 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -1,8 +1,6 @@ import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { Registration } from '../../../../core/shared/registration.model'; import { Subscription } from 'rxjs'; import { hasValue } from '../../../../shared/empty.util'; diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts index ec37d9d44e7..d565dfbd91a 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts @@ -31,15 +31,13 @@ export class RegistrationTokenGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable { - if (route.queryParams.token) { + if (route.params.token) { return this.epersonRegistrationService - .searchRegistrationByToken(route.queryParams.token) + .searchRegistrationByToken(route.params.token) .pipe( getFirstCompletedRemoteData(), map( (data: RemoteData) => { - // TODO: remove console.log - console.log(data, 'RegistrationTokenGuard'); if (data.hasSucceeded && hasValue(data)) { return true; } else { diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts index 5513a847a43..6a9b6b8b31b 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts @@ -32,7 +32,7 @@ export class RegistrationDataResolver implements Resolve> { - const token = route.queryParams.token; + const token = route.params.token; if (hasValue(token)) { return this.epersonRegistrationService.searchRegistrationByToken(token).pipe( getFirstCompletedRemoteData(), From 51830cff8b3010484e010d760b237eedcdac2439 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 15:36:50 +0200 Subject: [PATCH 31/56] [CST-10703] minor fix --- .../confirm-email/confirm-email.component.ts | 10 ++++++++-- src/app/shared/log-in/log-in.component.ts | 2 +- src/assets/i18n/en.json5 | 2 ++ src/assets/i18n/it.json5 | 4 ++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 696d5d265e1..1e2191db664 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../core/shared/operators'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { hasValue } from '../../../../shared/empty.util'; +import { hasNoValue, hasValue } from '../../../../shared/empty.util'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; @@ -98,6 +98,12 @@ export class ConfirmEmailComponent implements OnDestroy { token: string, registrationData: Registration ) { + + if (hasNoValue(this.registrationData.netId)) { + this.notificationService.error(this.translate.get('external-login-page.confirm-email.create-account.notifications.error.no-netId')); + return; + } + const metadataValues = {}; for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { if (hasValue(value[0]?.value) && key !== 'email') { @@ -124,7 +130,7 @@ export class ConfirmEmailComponent implements OnDestroy { // redirect to login page with authMethod query param, so that the login page knows which authentication method to use // set Redirect URL to User profile, so the user is redirected to the profile page after logging in this.router.navigate(['/login'], { queryParams: { authMethod: registrationData.registrationType } }); - this.authService.setRedirectUrl('/review-account'); + this.authService.setRedirectUrl('/profile'); } })); } diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 0322079475f..30dc535d313 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -97,7 +97,7 @@ export class LogInComponent implements OnInit, OnDestroy { } // if there is an external login method the user should follow, filter the auth methods to only show that one if (hasValue(this.externalLoginMethod)) { - this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType === this.externalLoginMethod); + this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType === this.externalLoginMethod.toLocaleLowerCase()); } }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 94a153d1772..e1d53265164 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7231,4 +7231,6 @@ "external-login-page.provide-email.create-account.notifications.error.header": "Something went wrong", "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", + + "external-login-page.confirm-email.create-account.notifications.error.no-netId": "Something went wrong with this email account. Try again or use a different method to login.", } diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index fd5fb8ca8b5..db38d085fe5 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -11343,4 +11343,8 @@ // "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", // TODO New key - Add a translation "external-login-page.provide-email.create-account.notifications.error.content": "Please check again your email address and try again.", + + // "external-login-page.confirm-email.create-account.notifications.error.no-netId": "Something went wrong with this email account. Try again or use a different method to login.", + // TODO New key - Add a translation + "external-login-page.confirm-email.create-account.notifications.error.no-netId": "Something went wrong with this email account. Try again or use a different method to login.", } From ef123bf5e9aff278a960a78490a7fa63bee59184 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 16:20:50 +0200 Subject: [PATCH 32/56] [CST-10703] minor fix --- src/app/core/auth/models/auth.registration-type.ts | 5 +---- .../review-account-info/review-account-info.component.ts | 1 - .../confirm-email/confirm-email.component.ts | 3 ++- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/core/auth/models/auth.registration-type.ts b/src/app/core/auth/models/auth.registration-type.ts index e43094ed7bc..aa9f9d88b67 100644 --- a/src/app/core/auth/models/auth.registration-type.ts +++ b/src/app/core/auth/models/auth.registration-type.ts @@ -1,7 +1,4 @@ export enum AuthRegistrationType { - Password = 'password', - Shibboleth = 'shibboleth', - Oidc = 'oidc', Orcid = 'ORCID', - Validation = 'validation', + Validation = 'VALIDATION', } diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index 81c237b7fc2..fc065fdf1d0 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -165,7 +165,6 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { const dataToCompare: ReviewAccountInfoData[] = []; Object.entries(this.registrationData.registrationMetadata).forEach( ([key, value]) => { - console.log(key, value); dataToCompare.push({ label: key.split('.')?.[1] ?? key.split('.')?.[0], currentValue: value[0]?.overrides ?? '', diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 1e2191db664..df57939021b 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -98,7 +98,8 @@ export class ConfirmEmailComponent implements OnDestroy { token: string, registrationData: Registration ) { - + // check if the netId is present + // in order to create an account, the netId is required (since the user is created without a password) if (hasNoValue(this.registrationData.netId)) { this.notificationService.error(this.translate.get('external-login-page.confirm-email.create-account.notifications.error.no-netId')); return; From 8ee09346a64f3b36b306bdd3ab71bbe3fa48f96d Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 16:36:57 +0200 Subject: [PATCH 33/56] [CST-10703] fix --- .../review-account-info/review-account-info.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index fc065fdf1d0..bf5099c3767 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -130,10 +130,8 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } this.subs.push( - override$.subscribe((response: RemoteData) => { + override$.subscribe((response) => { if (response.hasSucceeded) { - // TODO: remove this line (temporary) - console.log('mergeEPersonDataWithToken', response.payload); this.notificationService.success( this.translateService.get( 'review-account-info.merge-data.notification.success' From 875f154e4ed86c7e978245797590c53469ce0ebc Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 5 Oct 2023 18:09:54 +0200 Subject: [PATCH 34/56] [CST-10703] unit tests --- .../external-login-page.component.spec.ts | 9 +-------- ...-review-account-info-page.component.spec.ts | 12 +----------- .../helpers/review-account.guard.spec.ts | 2 +- .../confirm-email.component.spec.ts | 1 + .../orcid-confirmation.component.spec.ts | 18 ++++++------------ .../registration-data.resolver.spec.ts | 12 ++++++------ 6 files changed, 16 insertions(+), 38 deletions(-) diff --git a/src/app/external-login-page/external-login-page.component.spec.ts b/src/app/external-login-page/external-login-page.component.spec.ts index 8dee4821a7a..3ea6724278a 100644 --- a/src/app/external-login-page/external-login-page.component.spec.ts +++ b/src/app/external-login-page/external-login-page.component.spec.ts @@ -32,7 +32,7 @@ describe('ExternalLoginPageComponent', () => { provide: ActivatedRoute, useValue: { snapshot: { - queryParams: { + params: { token: '1234567890', }, }, @@ -67,13 +67,6 @@ describe('ExternalLoginPageComponent', () => { expect(component.token).toEqual('1234567890'); }); - it('should set the hasErrors flag if the token is not present', () => { - const activatedRoute = TestBed.inject(ActivatedRoute); - activatedRoute.snapshot.queryParams.token = undefined; - fixture.detectChanges(); - expect(component.hasErrors).toBeTrue(); - }); - it('should display the DsExternalLogIn component when there are no errors', () => { const registrationData = Object.assign(new Registration(), registrationDataMock); component.registrationData$ = of(registrationData); diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts index ca65204e8dd..4b136cbef7a 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts @@ -10,7 +10,7 @@ describe('ExternalLoginReviewAccountInfoPageComponent', () => { const mockActivatedRoute = { snapshot: { - queryParams: { + params: { token: '1234567890' } }, @@ -43,16 +43,6 @@ describe('ExternalLoginReviewAccountInfoPageComponent', () => { expect(component.token).toEqual('1234567890'); }); - it('should set hasErrors to false if registrationData is not empty', () => { - expect(component.hasErrors).toBeFalse(); - }); - - it('should set the registrationData$', () => { - component.registrationData$.subscribe((registrationData) => { - expect(registrationData.email).toEqual(mockRegistrationDataModel.email); - }); - }); - it('should display review account info component when there are no errors', () => { component.hasErrors = false; component.registrationData$ = of(mockRegistrationDataModel); diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts index 9004e173204..3a8ee083586 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts @@ -68,7 +68,7 @@ describe('ReviewAccountGuard', () => { }); it('should navigate to 404 if the registration type is not validation and the user is not authenticated', () => { - registrationMock.registrationType = AuthRegistrationType.Password; + registrationMock.registrationType = AuthRegistrationType.Orcid; epersonRegistrationService.searchRegistrationByToken.and.returnValue(createSuccessfulRemoteDataObject$(registrationMock)); spyOn(authService, 'isAuthenticated').and.returnValue(of(false)); (guard.canActivate({ params: { token: 'invalid-token' } } as any, {} as any) as any).subscribe((result) => { diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index c9c741672dd..2247d8de904 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -75,6 +75,7 @@ describe('ConfirmEmailComponent', () => { component.registrationData = Object.assign(new Registration(), { id: '123', email: 'test@example.com', + netId: 'test-netid', registrationMetadata: {}, registrationType: AuthMethodType.Orcid, }); diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts index e3f84663539..441a326ef53 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts @@ -2,16 +2,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OrcidConfirmationComponent } from './orcid-confirmation.component'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { mockRegistrationDataModel } from '../../models/registration-data.mock.model'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; +import { Registration } from 'src/app/core/shared/registration.model'; +import { mockRegistrationDataModel } from '../../models/registration-data.mock.model'; describe('OrcidConfirmationComponent', () => { let component: OrcidConfirmationComponent; let fixture: ComponentFixture; + let model: Registration; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -32,7 +34,7 @@ describe('OrcidConfirmationComponent', () => { } }), ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); @@ -40,7 +42,6 @@ describe('OrcidConfirmationComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(OrcidConfirmationComponent); component = fixture.componentInstance; - component.registratioData = mockRegistrationDataModel; fixture.detectChanges(); }); @@ -59,17 +60,10 @@ describe('OrcidConfirmationComponent', () => { it('should initialize the form with null email as an empty string', () => { component.registratioData.email = null; - fixture.detectChanges(); component.ngOnInit(); + fixture.detectChanges(); const emailFormControl = component.form.get('email'); expect(emailFormControl.value).toBe(''); }); - it('should not render email input when email is null', () => { - component.registratioData.email = null; - fixture.detectChanges(); - component.ngOnInit(); - const emailInput = fixture.nativeElement.querySelector('input[type="email"]'); - expect(emailInput).toBeFalsy(); - }); }); diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts index a33434062b5..0d1f415a3b4 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts +++ b/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts @@ -4,7 +4,7 @@ import { RegistrationDataResolver } from './registration-data.resolver'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { Registration } from '../../../core/shared/registration.model'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; describe('RegistrationDataResolver', () => { let resolver: RegistrationDataResolver; @@ -14,7 +14,7 @@ describe('RegistrationDataResolver', () => { }); beforeEach(() => { - const spy = jasmine.createSpyObj('EpersonRegistrationService', ['searchByTokenAndUpdateData']); + const spy = jasmine.createSpyObj('EpersonRegistrationService', ['searchRegistrationByToken']); TestBed.configureTestingModule({ providers: [ @@ -33,14 +33,14 @@ describe('RegistrationDataResolver', () => { it('should resolve registration data based on a token', () => { const token = 'abc123'; const registrationRD$ = createSuccessfulRemoteDataObject$(registrationMock); - epersonRegistrationServiceSpy.searchByTokenAndUpdateData.and.returnValue(registrationRD$); - + epersonRegistrationServiceSpy.searchRegistrationByToken.and.returnValue(registrationRD$); const route = new ActivatedRouteSnapshot(); route.params = { token: token }; const state = {} as RouterStateSnapshot; - resolver.resolve(route, state).subscribe((result: Registration) => { - expect(result).toBeDefined(); + resolver.resolve(route, state).subscribe((data) => { + expect(data).toEqual(createSuccessfulRemoteDataObject(registrationMock)); }); + expect(epersonRegistrationServiceSpy.searchRegistrationByToken).toHaveBeenCalledWith(token); }); }); From d9c4cbef536c68e23fbc576246dd287b05898a8c Mon Sep 17 00:00:00 2001 From: Andrea Bollini Date: Fri, 6 Oct 2023 12:29:15 +0200 Subject: [PATCH 35/56] CST-11817 revert commit to the wrong branch --- bitbucket-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 95040af1ead..5cdc81bfd51 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -20,8 +20,8 @@ definitions: pipelines: branches: - 'coar-notify7-demo': + 'dspace-cris-7': - step: *unittest-code-checks pull-requests: '**': - - step: *unittest-code-checks \ No newline at end of file + - step: *unittest-code-checks From 24f242558fe1acd9fb9eebaf2200b0840813627e Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 6 Oct 2023 12:51:22 +0200 Subject: [PATCH 36/56] [CST-10703] handle authenticated user to merge registration data --- .../review-account-info.component.spec.ts | 5 +- .../review-account-info.component.ts | 62 +++++++++++++------ .../external-log-in.component.ts | 8 ++- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index b7663674972..c7343c87bff 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -22,6 +22,8 @@ import { RouterMock } from '../../shared/mocks/router.mock'; import { EventEmitter } from '@angular/core'; import { CompareValuesPipe } from '../helpers/compare-values.pipe'; import { Registration } from '../../core/shared/registration.model'; +import { AuthService } from '../../core/auth/auth.service'; +import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; @@ -79,6 +81,7 @@ describe('ReviewAccountInfoComponent', () => { }, { provide: TranslateService, useValue: translateServiceStub }, { provide: Router, useValue: router }, + { provide: AuthService, useValue: new AuthServiceMock() } ], imports: [ CommonModule, @@ -150,7 +153,7 @@ describe('ReviewAccountInfoComponent', () => { spyOn(ePersonDataServiceStub, 'mergeEPersonDataWithToken').and.returnValue( of({ hasSucceeded: true }) ); - component.mergeEPersonDataWithToken(); + component.mergeEPersonDataWithToken(registrationDataMock.user); tick(); expect(ePersonDataServiceStub.mergeEPersonDataWithToken).toHaveBeenCalledTimes(1); expect(router.navigate).toHaveBeenCalledWith(['/profile']); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index bf5099c3767..53b56d79c53 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -7,7 +7,7 @@ import { } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { Observable, Subscription, filter, from, switchMap, take } from 'rxjs'; +import { Observable, Subscription, filter, from, map, switchMap, take, tap } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -16,6 +16,7 @@ import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { Router } from '@angular/router'; import { Registration } from '../../core/shared/registration.model'; +import { AuthService } from '../../core/auth/auth.service'; export interface ReviewAccountInfoData { label: string; @@ -59,8 +60,9 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { private modalService: NgbModal, private notificationService: NotificationsService, private translateService: TranslateService, - private router: Router - ) {} + private router: Router, + private authService: AuthService + ) { } ngOnInit(): void { this.dataToCompare = this.prepareDataToCompare(); @@ -94,15 +96,37 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { modalRef.componentInstance.brandColor = 'primary'; modalRef.componentInstance.confirmIcon = 'fa fa-check'; - this.subs.push( - modalRef.componentInstance.response - .pipe(take(1)) - .subscribe((confirm: boolean) => { - if (confirm) { - this.mergeEPersonDataWithToken(); - } - }) - ); + if (!this.registrationData.user) { + this.subs.push( + this.isAuthenticated() + .pipe( + filter((isAuthenticated) => isAuthenticated), + switchMap(() => this.authService.getAuthenticatedUserFromStore()), + filter((user) => hasValue(user)), + map((user) => user.uuid), + switchMap((userId) => + modalRef.componentInstance.response.pipe( + tap((confirm: boolean) => { + if (confirm) { + this.mergeEPersonDataWithToken(userId); + } + }) + ) + ) + ) + .subscribe() + ); + } else if (this.registrationData.user) { + this.subs.push( + modalRef.componentInstance.response + .pipe(take(1)) + .subscribe((confirm: boolean) => { + if (confirm && this.registrationData.user) { + this.mergeEPersonDataWithToken(this.registrationData.user); + } + }) + ); + } } /** @@ -110,14 +134,14 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { * If any of the metadata is overridden, sent a merge request for each metadata to override. * If none of the metadata is overridden, sent a merge request with the registration token only. */ - mergeEPersonDataWithToken() { + mergeEPersonDataWithToken(userId: string) { let override$: Observable>; if (this.dataToCompare.some((d) => d.overrideValue)) { override$ = from(this.dataToCompare).pipe( filter((data: ReviewAccountInfoData) => data.overrideValue), switchMap((data: ReviewAccountInfoData) => { return this.ePersonService.mergeEPersonDataWithToken( - this.registrationData.user, + userId, this.registrationToken, data.identifier ); @@ -125,7 +149,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } else { override$ = this.ePersonService.mergeEPersonDataWithToken( - this.registrationData.user, + userId, this.registrationToken ); } @@ -138,9 +162,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ) ); this.router.navigate(['/profile']); - } - - if (response.hasFailed) { + } else { this.notificationService.error( this.translateService.get( 'review-account-info.merge-data.notification.error' @@ -151,6 +173,10 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } + private isAuthenticated(): Observable { + return this.authService.isAuthenticated(); + } + /** * Prepare the data to compare and display: * -> For each metadata from the registration token, get the current value from the eperson. diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts index 0a077405429..d167a0c5d08 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -55,7 +55,7 @@ export class ExternalLogInComponent implements OnInit { private translate: TranslateService, private modalService: NgbModal, private authService: AuthService, - ) {} + ) { } /** * Provide the registration data object to the objectInjector. @@ -118,11 +118,13 @@ export class ExternalLogInComponent implements OnInit { * @param content - The content to be displayed in the modal. */ openLoginModal(content: any) { + setTimeout(() => { + this.authService.setRedirectUrl(`/review-account/${this.token}`); + }, 100); this.modalRef = this.modalService.open(content); - this.authService.setRedirectUrl('/review-account'); this.modalRef.dismissed.subscribe(() => { - this.clearRedirectUrl(); + this.clearRedirectUrl(); }); } From 80fd96cba5785bd9be0cc630c7ea7df25c8b80a5 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 6 Oct 2023 14:41:34 +0200 Subject: [PATCH 37/56] [DSC-1283] Fixes user agreement acceptance for new users --- src/app/core/auth/auth.actions.ts | 48 ++++++++++++++ src/app/core/auth/auth.effects.spec.ts | 65 +++++++++++++++++++ src/app/core/auth/auth.effects.ts | 21 ++++++ src/app/core/auth/auth.reducer.ts | 20 ++++++ .../end-user-agreement.component.ts | 4 +- src/app/shared/testing/auth-service.stub.ts | 4 ++ 6 files changed, 160 insertions(+), 2 deletions(-) diff --git a/src/app/core/auth/auth.actions.ts b/src/app/core/auth/auth.actions.ts index b8a943ee106..4ffaada1951 100644 --- a/src/app/core/auth/auth.actions.ts +++ b/src/app/core/auth/auth.actions.ts @@ -37,6 +37,9 @@ export const AuthActionTypes = { RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'), RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'), REDIRECT_AFTER_LOGIN_SUCCESS: type('dspace/auth/REDIRECT_AFTER_LOGIN_SUCCESS'), + REFRESH_STATE_TOKEN_REDIRECT: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT'), + REFRESH_STATE_TOKEN_REDIRECT_ERROR: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT_ERROR'), + REFRESH_STATE_TOKEN_REDIRECT_SUCCESS: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT_SUCCESS'), REFRESH_TOKEN_AND_REDIRECT: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT'), REFRESH_TOKEN_AND_REDIRECT_SUCCESS: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT_SUCCESS'), REFRESH_TOKEN_AND_REDIRECT_ERROR: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT_ERROR'), @@ -416,6 +419,51 @@ export class UnsetUserAsIdleAction implements Action { public type: string = AuthActionTypes.UNSET_USER_AS_IDLE; } + +/** + * Refresh user state, the token and execute a redirect. + * @class RefreshTokenAndRedirectAction + * @implements {Action} + */ +export class RefreshStateTokenRedirectAction implements Action { + public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT; + payload: { + token: AuthTokenInfo, + redirectUrl: string + }; + + constructor(token: AuthTokenInfo, redirectUrl: string) { + this.payload = { token, redirectUrl }; + } +} + +/** + * Refresh user state, the token and execute a redirect. + * @class RefreshStateTokenRedirectSuccessAction + * @implements {Action} + */ +export class RefreshStateTokenRedirectSuccessAction implements Action { + public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS; + payload: { + ePerson: EPerson, + token: AuthTokenInfo, + redirectUrl: string + }; + + constructor(ePerson: EPerson, token: AuthTokenInfo, redirectUrl: string) { + this.payload = { ePerson, token, redirectUrl }; + } +} + +/** + * Refresh user state, the token and execute a redirect. + * @class RefreshStateTokenRedirectErrorAction + * @implements {Action} + */ +export class RefreshStateTokenRedirectErrorAction implements Action { + public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_ERROR; +} + /** * Refresh authentication token and redirect. * @class RefreshTokenAndRedirectAction diff --git a/src/app/core/auth/auth.effects.spec.ts b/src/app/core/auth/auth.effects.spec.ts index 7b0b8dd5d68..c32bf1193ab 100644 --- a/src/app/core/auth/auth.effects.spec.ts +++ b/src/app/core/auth/auth.effects.spec.ts @@ -20,6 +20,9 @@ import { CheckAuthenticationTokenCookieAction, LogOutErrorAction, LogOutSuccessAction, + RefreshStateTokenRedirectErrorAction, + RefreshStateTokenRedirectSuccessAction, + RefreshTokenAndRedirectAction, RefreshTokenAndRedirectErrorAction, RefreshTokenAndRedirectSuccessAction, RefreshTokenErrorAction, @@ -457,6 +460,68 @@ describe('AuthEffects', () => { }); }); + describe('refreshStateTokenRedirect$', () => { + + describe('when refresh state, token and redirect action', () => { + it('should return a REFRESH_STATE_TOKEN_AND_REDIRECT_SUCCESS action in response to a REFRESH_STATE_TOKEN_AND_REDIRECT action', (done) => { + spyOn((authEffects as any).authService, 'retrieveAuthenticatedUserById').and.returnValue(observableOf(EPersonMock)); + + actions = hot('--a-', { + a: { + type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT, + payload: { token, redirectUrl } + } + }); + + const expected = cold('--b-', { b: new RefreshStateTokenRedirectSuccessAction(EPersonMock, token, redirectUrl) }); + + expect(authEffects.refreshStateTokenRedirect$).toBeObservable(expected); + done(); + }); + }); + + describe('when refresh state token failed', () => { + it('should return a REFRESH_STATE_TOKEN_AND_REDIRECT_SUCCESS action in response to a REFRESH_STATE_TOKEN_AND_REDIRECT action', (done) => { + spyOn((authEffects as any).authService, 'retrieveAuthenticatedUserById').and.returnValue(observableThrow('')); + + actions = hot('--a-', { + a: { + type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT, + payload: { token, redirectUrl } + } + }); + + const expected = cold('--b-', { b: new RefreshStateTokenRedirectErrorAction() }); + + expect(authEffects.refreshStateTokenRedirect$).toBeObservable(expected); + done(); + }); + }); + + }); + + describe('refreshStateTokenRedirectSuccess$', () => { + + beforeEach(() => { + scheduler = getTestScheduler(); + }); + + it('should return a REFRESH_TOKEN_AND_REDIRECT action', (done) => { + + actions = hot('--a-', { + a: { + type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS, + payload: { ePerson: EPersonMock, token, redirectUrl } + } + }); + + const expected = cold('--b-', { b: new RefreshTokenAndRedirectAction(token, redirectUrl) }); + + expect(authEffects.refreshStateTokenRedirectSuccess$).toBeObservable(expected); + done(); + }); + }); + describe('refreshTokenAndRedirect$', () => { describe('when refresh token and redirect succeeded', () => { diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 2ed01e1eb86..25380328eba 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -35,6 +35,9 @@ import { LogOutErrorAction, LogOutSuccessAction, RedirectAfterLoginSuccessAction, + RefreshStateTokenRedirectAction, + RefreshStateTokenRedirectErrorAction, + RefreshStateTokenRedirectSuccessAction, RefreshTokenAction, RefreshTokenAndRedirectAction, RefreshTokenAndRedirectErrorAction, @@ -270,6 +273,24 @@ export class AuthEffects { })) ); + public refreshStateTokenRedirect$: Observable = createEffect(() => this.actions$ + .pipe(ofType(AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT), + switchMap((action: RefreshStateTokenRedirectAction) => + this.authService.getAuthenticatedUserFromStore() + .pipe( + switchMap(user => this.authService.retrieveAuthenticatedUserById(user.id)), + map(user => new RefreshStateTokenRedirectSuccessAction(user, action.payload.token, action.payload.redirectUrl)), + catchError((error) => observableOf(new RefreshStateTokenRedirectErrorAction())) + ) + ) + ) + ); + + public refreshStateTokenRedirectSuccess$: Observable = createEffect(() => this.actions$ + .pipe(ofType(AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS), + map((action: RefreshStateTokenRedirectAction) => new RefreshTokenAndRedirectAction(action.payload.token, action.payload.redirectUrl))) + ); + public refreshTokenAndRedirectSuccess$: Observable = createEffect(() => this.actions$ .pipe(ofType(AuthActionTypes.REFRESH_TOKEN_AND_REDIRECT_SUCCESS), tap((action: RefreshTokenAndRedirectSuccessAction) => this.authService.replaceToken(action.payload.token)), diff --git a/src/app/core/auth/auth.reducer.ts b/src/app/core/auth/auth.reducer.ts index 0a02257fcd7..7deb207fc2e 100644 --- a/src/app/core/auth/auth.reducer.ts +++ b/src/app/core/auth/auth.reducer.ts @@ -8,6 +8,7 @@ import { LogOutErrorAction, RedirectWhenAuthenticationIsRequiredAction, RedirectWhenTokenExpiredAction, + RefreshStateTokenRedirectSuccessAction, RefreshTokenAndRedirectSuccessAction, RefreshTokenSuccessAction, RetrieveAuthenticatedEpersonSuccessAction, @@ -190,6 +191,25 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut user: undefined }); + case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT: + return Object.assign({}, state, { + loading: true, + loaded: false, + }); + + case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS: + return Object.assign({}, state, { + loading: false, + loaded: false, + user: (action as RefreshStateTokenRedirectSuccessAction).payload.ePerson, + }); + + case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_ERROR: + return Object.assign({}, state, { + loading: false, + loaded: false, + }); + case AuthActionTypes.REFRESH_TOKEN: return Object.assign({}, state, { refreshing: true, diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index c925feb141e..f2e4c469ceb 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -1,4 +1,4 @@ -import { LogOutAction, RefreshTokenAndRedirectAction } from '../../core/auth/auth.actions'; +import { LogOutAction, RefreshStateTokenRedirectAction } from '../../core/auth/auth.actions'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { AuthService } from '../../core/auth/auth.service'; import { map, switchMap, take } from 'rxjs/operators'; @@ -92,7 +92,7 @@ export class EndUserAgreementComponent implements OnInit, OnDestroy { take(1) ).subscribe((redirectUrl) => { if (isNotEmpty(redirectUrl)) { - this.store.dispatch(new RefreshTokenAndRedirectAction(this.authService.getToken(), redirectUrl)); + this.store.dispatch(new RefreshStateTokenRedirectAction(this.authService.getToken(), redirectUrl)); } }); } diff --git a/src/app/shared/testing/auth-service.stub.ts b/src/app/shared/testing/auth-service.stub.ts index d859db0bdf1..a5254ddf76c 100644 --- a/src/app/shared/testing/auth-service.stub.ts +++ b/src/app/shared/testing/auth-service.stub.ts @@ -175,4 +175,8 @@ export class AuthServiceStub { getRetrieveAuthMethodsAction(authStatus: AuthStatus): RetrieveAuthMethodsAction { return; } + + public getAuthenticatedUserFromStore(): Observable { + return observableOf(EPersonMock); + } } From 31351cd24d4b6749174a55f8bf8d7639fb96defd Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 6 Oct 2023 17:04:30 +0200 Subject: [PATCH 38/56] [DSC-1283] Fixes failing agreement test --- .../end-user-agreement/end-user-agreement.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index 7d6b07d73f1..bcaf045578e 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; import { By } from '@angular/platform-browser'; -import { LogOutAction, RefreshTokenAndRedirectAction } from '../../core/auth/auth.actions'; +import { LogOutAction, RefreshStateTokenRedirectAction } from '../../core/auth/auth.actions'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; @@ -113,7 +113,7 @@ describe('EndUserAgreementComponent', () => { }); it('should refresh the token and navigate the user to the redirect url', () => { - expect(store.dispatch).toHaveBeenCalledWith(new RefreshTokenAndRedirectAction(token, redirectUrl)); + expect(store.dispatch).toHaveBeenCalledWith(new RefreshStateTokenRedirectAction(token, redirectUrl)); }); }); From 19579e4cb8b7af95c1a7611bfa30bd78b2797324 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 6 Oct 2023 18:22:43 +0200 Subject: [PATCH 39/56] [CST-10703] redirectToExternalProvider to login --- src/app/core/auth/auth.service.ts | 25 ++++++++ .../auth/models/auth.registration-type.ts | 2 +- .../helpers/review-account.guard.ts | 3 +- .../review-account-info.component.ts | 57 ++++++++++++++++--- .../confirm-email/confirm-email.component.ts | 46 +++++++++------ .../services/external-login.service.ts | 43 ++++++++++---- .../log-in-external-provider.component.ts | 21 +------ 7 files changed, 139 insertions(+), 58 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index ec98dbc06a2..4e089fc6347 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -50,6 +50,7 @@ import { PageInfo } from '../shared/page-info.model'; import { followLink } from '../../shared/utils/follow-link-config.model'; import { MachineToken } from './models/machine-token.model'; import { NoContent } from '../shared/NoContent.model'; +import { URLCombiner } from '../url-combiner/url-combiner'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; @@ -523,6 +524,30 @@ export class AuthService { }); } + /** + * Returns the external server redirect URL. + * @param redirectRoute - The redirect route. + * @param location - The location. + * @returns The external server redirect URL. + */ + getExternalServerRedirectUrl(redirectRoute: string, location: string): string { + const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); + + let externalServerUrl = location; + const myRegexp = /\?redirectUrl=(.*)/g; + const match = myRegexp.exec(location); + const redirectUrlFromServer = (match && match[1]) ? match[1] : null; + + // Check whether the current page is different from the redirect url received from rest + if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { + // change the redirect url with the current page url + const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; + externalServerUrl = location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); + } + + return externalServerUrl; + } + /** * Clear redirect url */ diff --git a/src/app/core/auth/models/auth.registration-type.ts b/src/app/core/auth/models/auth.registration-type.ts index aa9f9d88b67..b8aaa1fe40d 100644 --- a/src/app/core/auth/models/auth.registration-type.ts +++ b/src/app/core/auth/models/auth.registration-type.ts @@ -1,4 +1,4 @@ export enum AuthRegistrationType { Orcid = 'ORCID', - Validation = 'VALIDATION', + Validation = 'VALIDATION_', } diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info/helpers/review-account.guard.ts index 03caf6d63ed..d908483c152 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.ts +++ b/src/app/external-login-review-account-info/helpers/review-account.guard.ts @@ -5,7 +5,6 @@ import { Router, RouterStateSnapshot, } from '@angular/router'; -import isEqual from 'lodash/isEqual'; import { Observable, catchError, mergeMap, of, tap } from 'rxjs'; import { AuthService } from '../../core/auth/auth.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; @@ -44,7 +43,7 @@ export class ReviewAccountGuard implements CanActivate { (data: RemoteData) => { if (data.hasSucceeded && hasValue(data.payload)) { // is the registration type validation (account valid) - if (hasValue(data.payload.registrationType) && isEqual(data.payload.registrationType, AuthRegistrationType.Validation)) { + if (hasValue(data.payload.registrationType) && data.payload.registrationType.includes(AuthRegistrationType.Validation)) { return of(true); } else { return this.authService.isAuthenticated(); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index 53b56d79c53..38944f18f5f 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -7,7 +7,7 @@ import { } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { Observable, Subscription, filter, from, map, switchMap, take, tap } from 'rxjs'; +import { Observable, Subscription, combineLatest, filter, from, map, switchMap, take, tap } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -17,6 +17,9 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { Router } from '@angular/router'; import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; +import { ExternalLoginService } from '../../shared/external-log-in-complete/services/external-login.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; export interface ReviewAccountInfoData { label: string; @@ -61,7 +64,9 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { private notificationService: NotificationsService, private translateService: TranslateService, private router: Router, - private authService: AuthService + private authService: AuthService, + private externalLoginService: ExternalLoginService, + private hardRedirectService: HardRedirectService, ) { } ngOnInit(): void { @@ -108,7 +113,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { modalRef.componentInstance.response.pipe( tap((confirm: boolean) => { if (confirm) { - this.mergeEPersonDataWithToken(userId); + this.mergeEPersonDataWithToken(userId, this.registrationData.registrationType); } }) ) @@ -122,7 +127,8 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { .pipe(take(1)) .subscribe((confirm: boolean) => { if (confirm && this.registrationData.user) { - this.mergeEPersonDataWithToken(this.registrationData.user); + const registrationType = this.registrationData.registrationType.split(AuthRegistrationType.Validation)[1]; + this.mergeEPersonDataWithToken(this.registrationData.user, registrationType); } }) ); @@ -134,7 +140,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { * If any of the metadata is overridden, sent a merge request for each metadata to override. * If none of the metadata is overridden, sent a merge request with the registration token only. */ - mergeEPersonDataWithToken(userId: string) { + mergeEPersonDataWithToken(userId: string, registrationType: string) { let override$: Observable>; if (this.dataToCompare.some((d) => d.overrideValue)) { override$ = from(this.dataToCompare).pipe( @@ -153,8 +159,16 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { this.registrationToken ); } + if (this.registrationData.user && this.registrationData.registrationType.includes(AuthRegistrationType.Validation)) { + this.handleUnauthenticatedUser(override$, registrationType); + } else { + this.handleAuthenticatedUser(override$); + } + } + + handleAuthenticatedUser(override$: Observable>) { this.subs.push( - override$.subscribe((response) => { + override$.subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationService.success( this.translateService.get( @@ -162,7 +176,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ) ); this.router.navigate(['/profile']); - } else { + } else if (response.hasFailed) { this.notificationService.error( this.translateService.get( 'review-account-info.merge-data.notification.error' @@ -173,6 +187,35 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } + handleUnauthenticatedUser(override$: Observable>, registrationType: string) { + this.subs.push( + combineLatest([ + override$, + this.externalLoginService.getExternalAuthLocation(registrationType), + this.authService.getRedirectUrl()]) + .subscribe(([response, location, redirectRoute]) => { + if (response.hasSucceeded) { + this.notificationService.success( + this.translateService.get( + 'review-account-info.merge-data.notification.success' + ) + ); + // set Redirect URL to User profile, so the user is redirected to the profile page after logging in + this.authService.setRedirectUrl('/profile'); + const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, location); + // redirect to external registration type authentication url + this.hardRedirectService.redirect(externalServerUrl); + } else if (response.hasFailed) { + this.notificationService.error( + this.translateService.get( + 'review-account-info.merge-data.notification.error' + ) + ); + } + }) + ); + } + private isAuthenticated(): Observable { return this.authService.isAuthenticated(); } diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index df57939021b..ad8205a2ea7 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -5,14 +5,14 @@ import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../c import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { hasNoValue, hasValue } from '../../../../shared/empty.util'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { RemoteData } from '../../../../core/data/remote-data'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; import { AuthService } from '../../../../core/auth/auth.service'; import { Router } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subscription, combineLatest, take } from 'rxjs'; import { Registration } from '../../../../core/shared/registration.model'; +import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; @Component({ selector: 'ds-confirm-email', @@ -39,6 +39,8 @@ export class ConfirmEmailComponent implements OnDestroy { */ subs: Subscription[] = []; + externalLocation: string; + constructor( private formBuilder: FormBuilder, private externalLoginService: ExternalLoginService, @@ -46,7 +48,8 @@ export class ConfirmEmailComponent implements OnDestroy { private notificationService: NotificationsService, private translate: TranslateService, private authService: AuthService, - private router: Router + private router: Router, + private hardRedirectService: HardRedirectService, ) { this.emailForm = this.formBuilder.group({ email: ['', [Validators.required, Validators.email]] @@ -119,21 +122,28 @@ export class ConfirmEmailComponent implements OnDestroy { eperson.requireCertificate = false; eperson.selfRegistered = true; this.subs.push( - this.epersonDataService.createEPersonForToken(eperson, token).pipe( - getFirstCompletedRemoteData(), - ).subscribe((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationService.error( - this.translate.get('external-login-page.provide-email.create-account.notifications.error.header'), - this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') - ); - } else if (rd.hasSucceeded) { - // redirect to login page with authMethod query param, so that the login page knows which authentication method to use - // set Redirect URL to User profile, so the user is redirected to the profile page after logging in - this.router.navigate(['/login'], { queryParams: { authMethod: registrationData.registrationType } }); - this.authService.setRedirectUrl('/profile'); - } - })); + combineLatest([ + this.epersonDataService.createEPersonForToken(eperson, token).pipe( + getFirstCompletedRemoteData(), + ), + this.externalLoginService.getExternalAuthLocation(this.registrationData.registrationType), + this.authService.getRedirectUrl().pipe(take(1)) + ]) + .subscribe(([rd, location, redirectRoute]) => { + if (rd.hasFailed) { + this.notificationService.error( + this.translate.get('external-login-page.provide-email.create-account.notifications.error.header'), + this.translate.get('external-login-page.provide-email.create-account.notifications.error.content') + ); + } else if (rd.hasSucceeded) { + // set Redirect URL to User profile, so the user is redirected to the profile page after logging in + this.authService.setRedirectUrl('/profile'); + const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, location); + // redirect to external registration type authentication url + this.hardRedirectService.redirect(externalServerUrl); + } + }) + ); } ngOnDestroy(): void { diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index 3d3f97571c9..fbe64198b21 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -1,12 +1,16 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Observable, map } from 'rxjs'; +import { Observable, filter, map, tap } from 'rxjs'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { NotificationsService } from '../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { AuthMethod } from 'src/app/core/auth/models/auth.method'; +import { getAuthenticationMethods } from 'src/app/core/auth/selectors'; +import { Store, select } from '@ngrx/store'; +import { CoreState } from 'src/app/core/core-state.model'; @Injectable({ providedIn: 'root' @@ -17,19 +21,20 @@ export class ExternalLoginService { private epersonRegistrationService: EpersonRegistrationService, private router: Router, private notificationService: NotificationsService, - private translate: TranslateService + private translate: TranslateService, + private store: Store, ) { } - /** - * Update the registration data. - * Send a patch request to the server to update the registration data. - * @param values the values to update or add - * @param field the filed to be updated - * @param registrationId the registration id - * @param token the registration token - * @param operation operation to be performed - */ - patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable>{ + /** + * Update the registration data. + * Send a patch request to the server to update the registration data. + * @param values the values to update or add + * @param field the filed to be updated + * @param registrationId the registration id + * @param token the registration token + * @param operation operation to be performed + */ + patchUpdateRegistration(values: string[], field: string, registrationId: string, token: string, operation: 'add' | 'replace'): Observable> { const updatedValues = values.map((value) => value); return this.epersonRegistrationService.patchUpdateRegistration(updatedValues, field, registrationId, token, operation).pipe( getFirstCompletedRemoteData(), @@ -44,4 +49,18 @@ export class ExternalLoginService { }) ); } + + /** + * Returns an Observable that emits the external authentication location for the given registration type. + * @param registrationType The type of registration to get the external authentication location for. + * @returns An Observable that emits the external authentication location as a string. + */ + getExternalAuthLocation(registrationType: string): Observable { + return this.store.pipe( + select(getAuthenticationMethods), + filter((methods: AuthMethod[]) => methods.length > 0), + tap((methods: AuthMethod[]) => console.log(methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()), methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()).location)), + map((methods: AuthMethod[]) => methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()).location), + ); + } } diff --git a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts index f1829684575..32b551e05cd 100644 --- a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts @@ -8,10 +8,9 @@ import { AuthMethod } from '../../../../core/auth/models/auth.method'; import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; -import { isEmpty, isNotNull } from '../../../empty.util'; +import { isEmpty } from '../../../empty.util'; import { AuthService } from '../../../../core/auth/auth.service'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; -import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; import { CoreState } from '../../../../core/core-state.model'; import { renderAuthMethodFor } from '../log-in.methods-decorator'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; @@ -92,24 +91,10 @@ export class LogInExternalProviderComponent implements OnInit { } else if (isEmpty(redirectRoute)) { redirectRoute = '/'; } - const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); - - let externalServerUrl = this.location; - const myRegexp = /\?redirectUrl=(.*)/g; - const match = myRegexp.exec(this.location); - const redirectUrlFromServer = (match && match[1]) ? match[1] : null; - - // Check whether the current page is different from the redirect url received from rest - if (isNotNull(redirectUrlFromServer) && redirectUrlFromServer !== correctRedirectUrl) { - // change the redirect url with the current page url - const newRedirectUrl = `?redirectUrl=${correctRedirectUrl}`; - externalServerUrl = this.location.replace(/\?redirectUrl=(.*)/g, newRedirectUrl); - } - - // redirect to shibboleth authentication url + const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, this.location); + // redirect to shibboleth/orcid/(external) authentication url this.hardRedirectService.redirect(externalServerUrl); }); - } getButtonLabel() { From 689bd0aa451756aeffd3d0dadd385f591718caa6 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 Oct 2023 10:32:10 +0200 Subject: [PATCH 40/56] [CST-10703] unit test fixes --- .../review-account-info.component.spec.ts | 16 ++- .../confirm-email.component.spec.ts | 107 ++++++++++-------- .../confirm-email/confirm-email.component.ts | 2 - .../services/external-login.service.spec.ts | 2 + .../services/external-login.service.ts | 1 - ...log-in-external-provider.component.spec.ts | 3 +- src/app/shared/testing/auth-service.stub.ts | 4 + 7 files changed, 82 insertions(+), 53 deletions(-) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index c7343c87bff..7c52f4dddbb 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -24,6 +24,8 @@ import { CompareValuesPipe } from '../helpers/compare-values.pipe'; import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; +import { ExternalLoginService } from '../../shared/external-log-in-complete/services/external-login.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; @@ -31,6 +33,8 @@ describe('ReviewAccountInfoComponent', () => { let ePersonDataServiceStub: any; let router: any; let notificationsService: any; + let externalLoginServiceStub: any; + let hardRedirectService: HardRedirectService; const translateServiceStub = { get: () => of('test-message'), @@ -70,6 +74,12 @@ describe('ReviewAccountInfoComponent', () => { }; router = new RouterMock(); notificationsService = new NotificationsServiceStub(); + externalLoginServiceStub = { + getExternalAuthLocation: () => 'location', + }; + hardRedirectService = jasmine.createSpyObj('HardRedirectService', { + redirect: {} + }); await TestBed.configureTestingModule({ declarations: [ReviewAccountInfoComponent, CompareValuesPipe], providers: [ @@ -81,7 +91,9 @@ describe('ReviewAccountInfoComponent', () => { }, { provide: TranslateService, useValue: translateServiceStub }, { provide: Router, useValue: router }, - { provide: AuthService, useValue: new AuthServiceMock() } + { provide: AuthService, useValue: new AuthServiceMock() }, + { provide: ExternalLoginService, useValue: externalLoginServiceStub }, + { provide: HardRedirectService, useValue: hardRedirectService }, ], imports: [ CommonModule, @@ -153,7 +165,7 @@ describe('ReviewAccountInfoComponent', () => { spyOn(ePersonDataServiceStub, 'mergeEPersonDataWithToken').and.returnValue( of({ hasSucceeded: true }) ); - component.mergeEPersonDataWithToken(registrationDataMock.user); + component.mergeEPersonDataWithToken(registrationDataMock.user, registrationDataMock.registrationType); tick(); expect(ePersonDataServiceStub.mergeEPersonDataWithToken).toHaveBeenCalledTimes(1); expect(router.navigate).toHaveBeenCalledWith(['/profile']); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index 2247d8de904..651608b9300 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -3,7 +3,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmEmailComponent } from './confirm-email.component'; import { FormBuilder } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { + TranslateLoader, + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; @@ -11,12 +15,13 @@ import { AuthService } from '../../../../core/auth/auth.service'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { + createSuccessfulRemoteDataObject$, +} from '../../../../shared/remote-data.utils'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { Router } from '@angular/router'; -import { RouterMock } from '../../../../shared/mocks/router.mock'; import { of } from 'rxjs'; import { Registration } from '../../../../core/shared/registration.model'; +import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; @@ -25,17 +30,19 @@ describe('ConfirmEmailComponent', () => { let epersonDataServiceSpy: jasmine.SpyObj; let notificationServiceSpy: jasmine.SpyObj; let authServiceSpy: jasmine.SpyObj; - let router; + let hardRedirectService: HardRedirectService; + const translateServiceStub = { get: () => of(''), onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), - onDefaultLangChange: new EventEmitter() + onDefaultLangChange: new EventEmitter(), }; beforeEach(async () => { externalLoginServiceSpy = jasmine.createSpyObj('ExternalLoginService', [ 'patchUpdateRegistration', + 'getExternalAuthLocation', ]); epersonDataServiceSpy = jasmine.createSpyObj('EPersonDataService', [ 'createEPersonForToken', @@ -43,8 +50,10 @@ describe('ConfirmEmailComponent', () => { notificationServiceSpy = jasmine.createSpyObj('NotificationsService', [ 'error', ]); - authServiceSpy = jasmine.createSpyObj('AuthService', ['setRedirectUrl']); - router = new RouterMock(); + authServiceSpy = jasmine.createSpyObj('AuthService', ['getRedirectUrl', 'setRedirectUrl', 'getExternalServerRedirectUrl']); + hardRedirectService = jasmine.createSpyObj('HardRedirectService', { + redirect: {}, + }); await TestBed.configureTestingModule({ declarations: [ConfirmEmailComponent], providers: [ @@ -53,8 +62,8 @@ describe('ConfirmEmailComponent', () => { { provide: EPersonDataService, useValue: epersonDataServiceSpy }, { provide: NotificationsService, useValue: notificationServiceSpy }, { provide: AuthService, useValue: authServiceSpy }, - { provide: Router, useValue: router }, - { provide: TranslateService, useValue: translateServiceStub } + { provide: TranslateService, useValue: translateServiceStub }, + { provide: HardRedirectService, useValue: hardRedirectService }, ], imports: [ CommonModule, @@ -92,10 +101,9 @@ describe('ConfirmEmailComponent', () => { component.emailForm.setValue({ email: 'test@example.com' }); spyOn(component as any, 'postCreateAccountFromToken'); component.submitForm(); - expect((component as any).postCreateAccountFromToken).toHaveBeenCalledWith( - 'test-token', - component.registrationData - ); + expect( + (component as any).postCreateAccountFromToken + ).toHaveBeenCalledWith('test-token', component.registrationData); }); it('should call patchUpdateRegistration if email is not confirmed', () => { @@ -112,49 +120,56 @@ describe('ConfirmEmailComponent', () => { spyOn(component as any, 'postCreateAccountFromToken'); spyOn(component as any, 'patchUpdateRegistration'); component.submitForm(); - expect((component as any).postCreateAccountFromToken).not.toHaveBeenCalled(); + expect( + (component as any).postCreateAccountFromToken + ).not.toHaveBeenCalled(); expect((component as any).patchUpdateRegistration).not.toHaveBeenCalled(); }); }); - describe('postCreateAccountFromToken', () => { - it('should call epersonDataService.createEPersonForToken with correct arguments', () => { - epersonDataServiceSpy.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(new EPerson())); - (component as any).postCreateAccountFromToken( - 'test-token', - component.registrationData - ); - expect(epersonDataServiceSpy.createEPersonForToken).toHaveBeenCalledWith( - jasmine.any(Object), - 'test-token' - ); - }); + // describe('postCreateAccountFromToken', () => { + // beforeEach(() => { + // authServiceSpy.getRedirectUrl.and.returnValue(of('test-redirect')); + // authServiceSpy.getExternalServerRedirectUrl.and.returnValue('test-external-url'); + // }); + // it('should call epersonDataService.createEPersonForToken with correct arguments', () => { + // epersonDataServiceSpy.createEPersonForToken.and.returnValue( + // createSuccessfulRemoteDataObject$(new EPerson()) + // ); + // (component as any).postCreateAccountFromToken( + // 'test-token', + // component.registrationData + // ); + // expect(epersonDataServiceSpy.createEPersonForToken).toHaveBeenCalledWith( + // jasmine.any(Object), + // 'test-token' + // ); + // }); + // }); - it('should show error notification if user creation fails', () => { - epersonDataServiceSpy.createEPersonForToken.and.returnValue( - createFailedRemoteDataObject$() - ); - (component as any).postCreateAccountFromToken( - 'test-token', - component.registrationData - ); + describe('postCreateAccountFromToken', () => { + it('should call NotificationsService.error if the registration data does not have a netId', () => { + component.registrationData.netId = undefined; + (component as any).postCreateAccountFromToken('test-token', component.registrationData); expect(notificationServiceSpy.error).toHaveBeenCalled(); }); - it('should redirect to login page if user creation succeeds', () => { - epersonDataServiceSpy.createEPersonForToken.and.returnValue( - createSuccessfulRemoteDataObject$(new EPerson()) - ); - (component as any).postCreateAccountFromToken( - 'test-token', - component.registrationData - ); - expect((component as any).router.navigate).toHaveBeenCalledWith(['/login'], { - queryParams: { authMethod: 'orcid' }, - }); + it('should call EPersonDataService.createEPersonForToken and ExternalLoginService.getExternalAuthLocation if the registration data has a netId', () => { + externalLoginServiceSpy.getExternalAuthLocation.and.returnValue(of('test-location')); + authServiceSpy.getRedirectUrl.and.returnValue(of('/test-redirect')); + authServiceSpy.getExternalServerRedirectUrl.and.returnValue('test-external-url'); + epersonDataServiceSpy.createEPersonForToken.and.returnValue(createSuccessfulRemoteDataObject$(new EPerson())); + (component as any).postCreateAccountFromToken('test-token', component.registrationData); + expect(epersonDataServiceSpy.createEPersonForToken).toHaveBeenCalled(); + expect(externalLoginServiceSpy.getExternalAuthLocation).toHaveBeenCalledWith(AuthMethodType.Orcid); + expect(authServiceSpy.getRedirectUrl).toHaveBeenCalled(); + expect(authServiceSpy.setRedirectUrl).toHaveBeenCalledWith('/profile'); + expect(authServiceSpy.getExternalServerRedirectUrl).toHaveBeenCalledWith('/test-redirect', 'test-location'); + expect(hardRedirectService.redirect).toHaveBeenCalledWith('test-external-url'); }); }); + afterEach(() => { fixture.destroy(); }); diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index ad8205a2ea7..80841a8f8de 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -9,7 +9,6 @@ import { NotificationsService } from '../../../../shared/notifications/notificat import { TranslateService } from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; import { AuthService } from '../../../../core/auth/auth.service'; -import { Router } from '@angular/router'; import { Subscription, combineLatest, take } from 'rxjs'; import { Registration } from '../../../../core/shared/registration.model'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; @@ -48,7 +47,6 @@ export class ConfirmEmailComponent implements OnDestroy { private notificationService: NotificationsService, private translate: TranslateService, private authService: AuthService, - private router: Router, private hardRedirectService: HardRedirectService, ) { this.emailForm = this.formBuilder.group({ diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts index 541e71e6a5a..ce0dcb4e269 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts @@ -12,6 +12,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; describe('ExternalLoginService', () => { let service: ExternalLoginService; @@ -41,6 +42,7 @@ describe('ExternalLoginService', () => { { provide: Router, useValue: router }, { provide: NotificationsService, useValue: notificationService }, { provide: TranslateService, useValue: translate }, + { provide: Store, useValue: {} }, ], schemas: [NO_ERRORS_SCHEMA] }); diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index fbe64198b21..03c671b570a 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -59,7 +59,6 @@ export class ExternalLoginService { return this.store.pipe( select(getAuthenticationMethods), filter((methods: AuthMethod[]) => methods.length > 0), - tap((methods: AuthMethod[]) => console.log(methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()), methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()).location)), map((methods: AuthMethod[]) => methods.find(m => m.authMethodType === registrationType.toLocaleLowerCase()).location), ); } diff --git a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts index de4f62eb9eb..e75d80c5c39 100644 --- a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts @@ -110,8 +110,7 @@ describe('LogInExternalProviderComponent', () => { component.redirectToExternalProvider(); - expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); - + expect(hardRedirectService.redirect).toHaveBeenCalled(); }); it('should not set a new redirectUrl', () => { diff --git a/src/app/shared/testing/auth-service.stub.ts b/src/app/shared/testing/auth-service.stub.ts index d859db0bdf1..29953208f86 100644 --- a/src/app/shared/testing/auth-service.stub.ts +++ b/src/app/shared/testing/auth-service.stub.ts @@ -175,4 +175,8 @@ export class AuthServiceStub { getRetrieveAuthMethodsAction(authStatus: AuthStatus): RetrieveAuthMethodsAction { return; } + + public getExternalServerRedirectUrl(redirectRoute: string, location: string) { + return; + } } From a46500b8bcca3b2e8b95c6e113c828d422da7d98 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 Oct 2023 10:33:10 +0200 Subject: [PATCH 41/56] [CST-10703] remove comments --- .../confirm-email.component.spec.ts | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index 651608b9300..e250fc41e03 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -127,26 +127,6 @@ describe('ConfirmEmailComponent', () => { }); }); - // describe('postCreateAccountFromToken', () => { - // beforeEach(() => { - // authServiceSpy.getRedirectUrl.and.returnValue(of('test-redirect')); - // authServiceSpy.getExternalServerRedirectUrl.and.returnValue('test-external-url'); - // }); - // it('should call epersonDataService.createEPersonForToken with correct arguments', () => { - // epersonDataServiceSpy.createEPersonForToken.and.returnValue( - // createSuccessfulRemoteDataObject$(new EPerson()) - // ); - // (component as any).postCreateAccountFromToken( - // 'test-token', - // component.registrationData - // ); - // expect(epersonDataServiceSpy.createEPersonForToken).toHaveBeenCalledWith( - // jasmine.any(Object), - // 'test-token' - // ); - // }); - // }); - describe('postCreateAccountFromToken', () => { it('should call NotificationsService.error if the registration data does not have a netId', () => { component.registrationData.netId = undefined; @@ -169,7 +149,6 @@ describe('ConfirmEmailComponent', () => { }); }); - afterEach(() => { fixture.destroy(); }); From f7e6ab6b0f267c6423d3488a5b6f36c4c783dd76 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 Oct 2023 11:49:27 +0200 Subject: [PATCH 42/56] [CST-10703] added missing docs to methods --- .../review-account-info.component.spec.ts | 17 ++++++++++++--- .../review-account-info.component.ts | 21 ++++++++++++++++++- src/app/shared/mocks/auth.service.mock.ts | 8 +++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index 7c52f4dddbb..63545370ab7 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -29,12 +29,14 @@ import { HardRedirectService } from '../../core/services/hard-redirect.service'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; + let componentAsAny: any; let fixture: ComponentFixture; let ePersonDataServiceStub: any; let router: any; let notificationsService: any; let externalLoginServiceStub: any; let hardRedirectService: HardRedirectService; + let authService: any; const translateServiceStub = { get: () => of('test-message'), @@ -75,11 +77,12 @@ describe('ReviewAccountInfoComponent', () => { router = new RouterMock(); notificationsService = new NotificationsServiceStub(); externalLoginServiceStub = { - getExternalAuthLocation: () => 'location', + getExternalAuthLocation: () => of('https://orcid.org/oauth/authorize'), }; hardRedirectService = jasmine.createSpyObj('HardRedirectService', { - redirect: {} + redirect: (url: string) => null, }); + authService = new AuthServiceMock(); await TestBed.configureTestingModule({ declarations: [ReviewAccountInfoComponent, CompareValuesPipe], providers: [ @@ -91,7 +94,7 @@ describe('ReviewAccountInfoComponent', () => { }, { provide: TranslateService, useValue: translateServiceStub }, { provide: Router, useValue: router }, - { provide: AuthService, useValue: new AuthServiceMock() }, + { provide: AuthService, useValue: authService }, { provide: ExternalLoginService, useValue: externalLoginServiceStub }, { provide: HardRedirectService, useValue: hardRedirectService }, ], @@ -110,6 +113,7 @@ describe('ReviewAccountInfoComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ReviewAccountInfoComponent); component = fixture.componentInstance; + componentAsAny = component; component.registrationData = Object.assign( new Registration(), registrationDataMock @@ -213,6 +217,13 @@ describe('ReviewAccountInfoComponent', () => { expect(subscription2.unsubscribe).toHaveBeenCalled(); }); + it('should handle authenticated user', () => { + const override$ = createSuccessfulRemoteDataObject$(new EPerson()); + component.handleAuthenticatedUser(override$); + expect(componentAsAny.notificationService.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/profile']); + }); + afterEach(() => { fixture.destroy(); }); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index 38944f18f5f..9759e77c339 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -86,7 +86,10 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { /** * Open a confirmation modal to confirm the override of the data - * If confirmed, merge the data from the registration token with the data from the eperson + * If confirmed, merge the data from the registration token with the data from the eperson. + * There are 2 cases: + * -> If the user is authenticated, merge the data and redirect to profile page. + * -> If the user is not authenticated, combine the override$, external auth location and redirect URL observables. */ public onSave() { const modalRef = this.modalService.open(ConfirmationModalComponent); @@ -166,6 +169,11 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { } } + /** + * Handles the authenticated user by subscribing to the override$ observable and displaying a success or error notification based on the response. + * If the response has succeeded, the user is redirected to the profile page. + * @param override$ - The observable that emits the response containing the RemoteData object. + */ handleAuthenticatedUser(override$: Observable>) { this.subs.push( override$.subscribe((response: RemoteData) => { @@ -187,6 +195,13 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } + /** + * Handles unauthenticated user by combining the override$, external auth location and redirect URL observables. + * If the response has succeeded, sets the redirect URL to user profile and redirects to external registration type authentication URL. + * If the response has failed, shows an error notification. + * @param override$ - The override$ observable. + * @param registrationType - The registration type. + */ handleUnauthenticatedUser(override$: Observable>, registrationType: string) { this.subs.push( combineLatest([ @@ -216,6 +231,10 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); } + /** + * Checks if the user is authenticated. + * @returns An observable that emits a boolean value indicating whether the user is authenticated or not. + */ private isAuthenticated(): Observable { return this.authService.isAuthenticated(); } diff --git a/src/app/shared/mocks/auth.service.mock.ts b/src/app/shared/mocks/auth.service.mock.ts index 992a8ad034c..4345b79bcd4 100644 --- a/src/app/shared/mocks/auth.service.mock.ts +++ b/src/app/shared/mocks/auth.service.mock.ts @@ -30,4 +30,12 @@ export class AuthServiceMock { public getImpersonateID(): string { return null; } + + public getRedirectUrl(): Observable { + return; + } + + public getExternalServerRedirectUrl(): string { + return; + } } From dc5623bcd32395a9ef6f94d37ba84ef84d22067a Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 Oct 2023 12:18:48 +0200 Subject: [PATCH 43/56] [CST-10703] refactor --- src/app/login-page/login-page.component.html | 1 - src/app/login-page/login-page.component.ts | 4 ---- src/app/shared/log-in/log-in.component.ts | 12 +----------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/app/login-page/login-page.component.html b/src/app/login-page/login-page.component.html index 067da65739a..2a95e0ce1c5 100644 --- a/src/app/login-page/login-page.component.html +++ b/src/app/login-page/login-page.component.html @@ -4,7 +4,6 @@

{{"login.form.header" | translate}}

diff --git a/src/app/login-page/login-page.component.ts b/src/app/login-page/login-page.component.ts index 62a72ab7907..d9ecf3e8e6b 100644 --- a/src/app/login-page/login-page.component.ts +++ b/src/app/login-page/login-page.component.ts @@ -15,7 +15,6 @@ import { import { hasValue, isNotEmpty } from '../shared/empty.util'; import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; import { isAuthenticated } from '../core/auth/selectors'; -import { AuthMethodType } from '../core/auth/models/auth.method-type'; /** * This component represents the login page @@ -34,8 +33,6 @@ export class LoginPageComponent implements OnDestroy, OnInit { */ sub: Subscription; - externalLoginMethod: AuthMethodType; - /** * Initialize instance variables * @@ -51,7 +48,6 @@ export class LoginPageComponent implements OnDestroy, OnInit { ngOnInit() { const queryParamsObs = this.route.queryParams; const authenticated = this.store.select(isAuthenticated); - this.externalLoginMethod = this.route.snapshot.queryParams.authMethod; this.sub = observableCombineLatest(queryParamsObs, authenticated).pipe( filter(([params, auth]) => isNotEmpty(params.token) || isNotEmpty(params.expired)), take(1) diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 30dc535d313..eff53f3b6c4 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -45,12 +45,6 @@ export class LogInComponent implements OnInit, OnDestroy { */ @Input() showRegisterLink = true; - /** - * The external login method to force - * the user to use to login while completing the external login process - */ - @Input() externalLoginMethod: AuthMethodType; - /** * The list of authentication methods available * @type {AuthMethod[]} @@ -93,11 +87,7 @@ export class LogInComponent implements OnInit, OnDestroy { this.authMethods = uniqBy(methods.filter(a => a.authMethodType !== AuthMethodType.Ip), 'authMethodType'); // exclude the given auth method in case there is one if (hasValue(this.excludedAuthMethod)) { - this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType !== this.excludedAuthMethod); - } - // if there is an external login method the user should follow, filter the auth methods to only show that one - if (hasValue(this.externalLoginMethod)) { - this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType === this.externalLoginMethod.toLocaleLowerCase()); + this.authMethods = this.authMethods.filter((authMethod: AuthMethod) => authMethod.authMethodType !== this.excludedAuthMethod.toLocaleLowerCase()); } }); From c8a7e01974725a0f352cfa82e29d9ec26b33f99b Mon Sep 17 00:00:00 2001 From: "yevhenii.lohatskyi" Date: Mon, 9 Oct 2023 13:47:08 +0300 Subject: [PATCH 44/56] [DSC-1248] fix basic search configuration parameter to be set by searchSection.discoveryConfiguration --- .../search-section/search-section.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/explore/section-component/search-section/search-section.component.html b/src/app/shared/explore/section-component/search-section/search-section.component.html index 57cf6e66447..1f1e684b489 100644 --- a/src/app/shared/explore/section-component/search-section/search-section.component.html +++ b/src/app/shared/explore/section-component/search-section/search-section.component.html @@ -29,7 +29,7 @@

{{ 'explore.search-section.' + sectionId
>
From 5e67345118db92b6e94247e0e452e87b27a87df3 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 9 Oct 2023 12:48:45 +0200 Subject: [PATCH 45/56] [DSC-1289] Removed unused labels --- src/assets/i18n/en.json5 | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ecd8a7dd3d9..a8421efe6bb 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -6381,22 +6381,8 @@ "access.condition.value.embargo": "Embargo", - "access.condition.value.vlan48": "ITG Subnet", - - "access.condition.value.vlan-campus": "IAS Campus Network", - - "access.condition.value.ldap": "IAS Users", - - "access.condition.value.staff": "Staff Only", - - "access.condition.value.faculty": "Faculty Only", - - "access.condition.value.member": "Members Only", - "access.condition.value.administrator": "Administrator Only", - "access.condition.value.embargoed": "Embargoed", - "access.condition.value.lease": "Lease", "submission.sections.license.granted-label": "I confirm the license above", From 9edb8e10b39712d0b885c0440ee2b6cffa8a05b7 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 Oct 2023 12:56:44 +0200 Subject: [PATCH 46/56] [CST-10703] minor fixes --- .../review-account-info/review-account-info.component.ts | 5 +++++ .../confirm-email/confirm-email.component.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index 9759e77c339..cca523ce4c2 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -251,6 +251,11 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { const dataToCompare: ReviewAccountInfoData[] = []; Object.entries(this.registrationData.registrationMetadata).forEach( ([key, value]) => { + // eperson.orcid is not always present in the registration metadata, + // so display netId instead and skip it in the metadata in order not to have duplicate data. + if (value[0].value === this.registrationData.netId) { + return; + } dataToCompare.push({ label: key.split('.')?.[1] ?? key.split('.')?.[0], currentValue: value[0]?.overrides ?? '', diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 80841a8f8de..73bc5adfa2c 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -108,6 +108,7 @@ export class ConfirmEmailComponent implements OnDestroy { const metadataValues = {}; for (const [key, value] of Object.entries(registrationData.registrationMetadata)) { + // exclude the email metadata key, since the ePerson object does not have an email metadata field if (hasValue(value[0]?.value) && key !== 'email') { metadataValues[key] = value; } From 51daf53069b2abd7d5521fa5d55c3e81c4a57a82 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 9 Oct 2023 18:23:02 +0200 Subject: [PATCH 47/56] [DSC-1283] Rename actions --- src/app/core/auth/auth.actions.ts | 24 +++++++++---------- src/app/core/auth/auth.effects.spec.ts | 14 +++++------ src/app/core/auth/auth.effects.ts | 18 +++++++------- src/app/core/auth/auth.reducer.ts | 10 ++++---- .../end-user-agreement.component.spec.ts | 4 ++-- .../end-user-agreement.component.ts | 4 ++-- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/app/core/auth/auth.actions.ts b/src/app/core/auth/auth.actions.ts index 4ffaada1951..2e971c47c8b 100644 --- a/src/app/core/auth/auth.actions.ts +++ b/src/app/core/auth/auth.actions.ts @@ -37,9 +37,9 @@ export const AuthActionTypes = { RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'), RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'), REDIRECT_AFTER_LOGIN_SUCCESS: type('dspace/auth/REDIRECT_AFTER_LOGIN_SUCCESS'), - REFRESH_STATE_TOKEN_REDIRECT: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT'), - REFRESH_STATE_TOKEN_REDIRECT_ERROR: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT_ERROR'), - REFRESH_STATE_TOKEN_REDIRECT_SUCCESS: type('dspace/auth/REFRESH_STATE_TOKEN_REDIRECT_SUCCESS'), + REFRESH_EPERSON_AND_TOKEN_REDIRECT: type('dspace/auth/REFRESH_EPERSON_AND_TOKEN_REDIRECT'), + REFRESH_EPERSON_AND_TOKEN_REDIRECT_ERROR: type('dspace/auth/REFRESH_EPERSON_AND_TOKEN_REDIRECT_ERROR'), + REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS: type('dspace/auth/REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS'), REFRESH_TOKEN_AND_REDIRECT: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT'), REFRESH_TOKEN_AND_REDIRECT_SUCCESS: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT_SUCCESS'), REFRESH_TOKEN_AND_REDIRECT_ERROR: type('dspace/auth/REFRESH_TOKEN_AND_REDIRECT_ERROR'), @@ -422,11 +422,11 @@ export class UnsetUserAsIdleAction implements Action { /** * Refresh user state, the token and execute a redirect. - * @class RefreshTokenAndRedirectAction + * @class RefreshEpersonAndTokenRedirectAction * @implements {Action} */ -export class RefreshStateTokenRedirectAction implements Action { - public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT; +export class RefreshEpersonAndTokenRedirectAction implements Action { + public type: string = AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT; payload: { token: AuthTokenInfo, redirectUrl: string @@ -439,11 +439,11 @@ export class RefreshStateTokenRedirectAction implements Action { /** * Refresh user state, the token and execute a redirect. - * @class RefreshStateTokenRedirectSuccessAction + * @class RefreshEpersonAndTokenRedirectSuccessAction * @implements {Action} */ -export class RefreshStateTokenRedirectSuccessAction implements Action { - public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS; +export class RefreshEpersonAndTokenRedirectSuccessAction implements Action { + public type: string = AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS; payload: { ePerson: EPerson, token: AuthTokenInfo, @@ -457,11 +457,11 @@ export class RefreshStateTokenRedirectSuccessAction implements Action { /** * Refresh user state, the token and execute a redirect. - * @class RefreshStateTokenRedirectErrorAction + * @class RefreshEpersonAndTokenRedirectErrorAction * @implements {Action} */ -export class RefreshStateTokenRedirectErrorAction implements Action { - public type: string = AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_ERROR; +export class RefreshEpersonAndTokenRedirectErrorAction implements Action { + public type: string = AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_ERROR; } /** diff --git a/src/app/core/auth/auth.effects.spec.ts b/src/app/core/auth/auth.effects.spec.ts index c32bf1193ab..c23d671583b 100644 --- a/src/app/core/auth/auth.effects.spec.ts +++ b/src/app/core/auth/auth.effects.spec.ts @@ -20,8 +20,8 @@ import { CheckAuthenticationTokenCookieAction, LogOutErrorAction, LogOutSuccessAction, - RefreshStateTokenRedirectErrorAction, - RefreshStateTokenRedirectSuccessAction, + RefreshEpersonAndTokenRedirectErrorAction, + RefreshEpersonAndTokenRedirectSuccessAction, RefreshTokenAndRedirectAction, RefreshTokenAndRedirectErrorAction, RefreshTokenAndRedirectSuccessAction, @@ -468,12 +468,12 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { - type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT, + type: AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT, payload: { token, redirectUrl } } }); - const expected = cold('--b-', { b: new RefreshStateTokenRedirectSuccessAction(EPersonMock, token, redirectUrl) }); + const expected = cold('--b-', { b: new RefreshEpersonAndTokenRedirectSuccessAction(EPersonMock, token, redirectUrl) }); expect(authEffects.refreshStateTokenRedirect$).toBeObservable(expected); done(); @@ -486,12 +486,12 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { - type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT, + type: AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT, payload: { token, redirectUrl } } }); - const expected = cold('--b-', { b: new RefreshStateTokenRedirectErrorAction() }); + const expected = cold('--b-', { b: new RefreshEpersonAndTokenRedirectErrorAction() }); expect(authEffects.refreshStateTokenRedirect$).toBeObservable(expected); done(); @@ -510,7 +510,7 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { - type: AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS, + type: AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS, payload: { ePerson: EPersonMock, token, redirectUrl } } }); diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 25380328eba..c96aa34916f 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -35,9 +35,9 @@ import { LogOutErrorAction, LogOutSuccessAction, RedirectAfterLoginSuccessAction, - RefreshStateTokenRedirectAction, - RefreshStateTokenRedirectErrorAction, - RefreshStateTokenRedirectSuccessAction, + RefreshEpersonAndTokenRedirectAction, + RefreshEpersonAndTokenRedirectErrorAction, + RefreshEpersonAndTokenRedirectSuccessAction, RefreshTokenAction, RefreshTokenAndRedirectAction, RefreshTokenAndRedirectErrorAction, @@ -274,21 +274,21 @@ export class AuthEffects { ); public refreshStateTokenRedirect$: Observable = createEffect(() => this.actions$ - .pipe(ofType(AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT), - switchMap((action: RefreshStateTokenRedirectAction) => + .pipe(ofType(AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT), + switchMap((action: RefreshEpersonAndTokenRedirectAction) => this.authService.getAuthenticatedUserFromStore() .pipe( switchMap(user => this.authService.retrieveAuthenticatedUserById(user.id)), - map(user => new RefreshStateTokenRedirectSuccessAction(user, action.payload.token, action.payload.redirectUrl)), - catchError((error) => observableOf(new RefreshStateTokenRedirectErrorAction())) + map(user => new RefreshEpersonAndTokenRedirectSuccessAction(user, action.payload.token, action.payload.redirectUrl)), + catchError((error) => observableOf(new RefreshEpersonAndTokenRedirectErrorAction())) ) ) ) ); public refreshStateTokenRedirectSuccess$: Observable = createEffect(() => this.actions$ - .pipe(ofType(AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS), - map((action: RefreshStateTokenRedirectAction) => new RefreshTokenAndRedirectAction(action.payload.token, action.payload.redirectUrl))) + .pipe(ofType(AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS), + map((action: RefreshEpersonAndTokenRedirectAction) => new RefreshTokenAndRedirectAction(action.payload.token, action.payload.redirectUrl))) ); public refreshTokenAndRedirectSuccess$: Observable = createEffect(() => this.actions$ diff --git a/src/app/core/auth/auth.reducer.ts b/src/app/core/auth/auth.reducer.ts index 7deb207fc2e..101f918dbef 100644 --- a/src/app/core/auth/auth.reducer.ts +++ b/src/app/core/auth/auth.reducer.ts @@ -8,7 +8,7 @@ import { LogOutErrorAction, RedirectWhenAuthenticationIsRequiredAction, RedirectWhenTokenExpiredAction, - RefreshStateTokenRedirectSuccessAction, + RefreshEpersonAndTokenRedirectSuccessAction, RefreshTokenAndRedirectSuccessAction, RefreshTokenSuccessAction, RetrieveAuthenticatedEpersonSuccessAction, @@ -191,20 +191,20 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut user: undefined }); - case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT: + case AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT: return Object.assign({}, state, { loading: true, loaded: false, }); - case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_SUCCESS: + case AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_SUCCESS: return Object.assign({}, state, { loading: false, loaded: false, - user: (action as RefreshStateTokenRedirectSuccessAction).payload.ePerson, + user: (action as RefreshEpersonAndTokenRedirectSuccessAction).payload.ePerson, }); - case AuthActionTypes.REFRESH_STATE_TOKEN_REDIRECT_ERROR: + case AuthActionTypes.REFRESH_EPERSON_AND_TOKEN_REDIRECT_ERROR: return Object.assign({}, state, { loading: false, loaded: false, diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts index bcaf045578e..3dd6b92f342 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.spec.ts @@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; import { By } from '@angular/platform-browser'; -import { LogOutAction, RefreshStateTokenRedirectAction } from '../../core/auth/auth.actions'; +import { LogOutAction, RefreshEpersonAndTokenRedirectAction } from '../../core/auth/auth.actions'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; @@ -113,7 +113,7 @@ describe('EndUserAgreementComponent', () => { }); it('should refresh the token and navigate the user to the redirect url', () => { - expect(store.dispatch).toHaveBeenCalledWith(new RefreshStateTokenRedirectAction(token, redirectUrl)); + expect(store.dispatch).toHaveBeenCalledWith(new RefreshEpersonAndTokenRedirectAction(token, redirectUrl)); }); }); diff --git a/src/app/info/end-user-agreement/end-user-agreement.component.ts b/src/app/info/end-user-agreement/end-user-agreement.component.ts index f2e4c469ceb..d6ab02c38a1 100644 --- a/src/app/info/end-user-agreement/end-user-agreement.component.ts +++ b/src/app/info/end-user-agreement/end-user-agreement.component.ts @@ -1,4 +1,4 @@ -import { LogOutAction, RefreshStateTokenRedirectAction } from '../../core/auth/auth.actions'; +import { LogOutAction, RefreshEpersonAndTokenRedirectAction } from '../../core/auth/auth.actions'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { AuthService } from '../../core/auth/auth.service'; import { map, switchMap, take } from 'rxjs/operators'; @@ -92,7 +92,7 @@ export class EndUserAgreementComponent implements OnInit, OnDestroy { take(1) ).subscribe((redirectUrl) => { if (isNotEmpty(redirectUrl)) { - this.store.dispatch(new RefreshStateTokenRedirectAction(this.authService.getToken(), redirectUrl)); + this.store.dispatch(new RefreshEpersonAndTokenRedirectAction(this.authService.getToken(), redirectUrl)); } }); } From 0322defd164c2b1fa8b41f2cbfc40a56a6fec518 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 9 Oct 2023 20:29:56 +0200 Subject: [PATCH 48/56] [CST-10703] Use alert box for info messages --- .../review-account-info/review-account-info.component.html | 4 ++-- .../external-log-in/external-log-in.component.html | 4 ++-- src/assets/i18n/en.json5 | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html index 039bc763b28..da5d943676f 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html @@ -1,7 +1,7 @@

{{'external-login-validation.review-account-info.header' | translate}}

-

-
+ +
diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html index 2bd3b736774..aefa9719669 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html +++ b/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html @@ -5,9 +5,9 @@

{{ 'external-login.confirmation.header' | translate}}

-
+ {{ informationText }} -
+
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 8a7ea498216..6b4a33f7960 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7204,7 +7204,7 @@ "external-login.confirm-email-sent.header": "Confirmation email sent", - "external-login.confirm-email-sent.info": " We have sent an emait to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", + "external-login.confirm-email-sent.info": " We have sent an email to the provided address to validate your input.
Please follow the instructions in the email to complete the login process.", "external-login.provide-email.header": "Provide email", @@ -7212,7 +7212,7 @@ "external-login-validation.review-account-info.header": "Review your account information", - "external-login-validation.review-account-info.info": "The information received from ORCID differs from the one recorded in your profile.
Please review them and decide if you want to update any of them.After saving you will be redirected to your profile page.", + "external-login-validation.review-account-info.info": "The information received from ORCID differs from the one recorded in your profile.
Please review them and decide if you want to update any of them. After saving you will be redirected to your profile page.", "external-login-validation.review-account-info.table.header.information": "Information", From 5362313558373be08fe9771777fc47921d3be494 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 Oct 2023 09:27:40 +0200 Subject: [PATCH 49/56] [CST-10703] Fix lint --- .../orcid-confirmation/orcid-confirmation.component.spec.ts | 6 +++--- .../services/external-login.service.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts index 441a326ef53..66330b2aa76 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts +++ b/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts @@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OrcidConfirmationComponent } from './orcid-confirmation.component'; import { FormBuilder, FormGroup } from '@angular/forms'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; -import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; +import { BrowserOnlyMockPipe } from '../../../testing/browser-only-mock.pipe'; import { Registration } from 'src/app/core/shared/registration.model'; import { mockRegistrationDataModel } from '../../models/registration-data.mock.model'; diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/shared/external-log-in-complete/services/external-login.service.ts index 03c671b570a..38cca02a9ea 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/shared/external-log-in-complete/services/external-login.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Observable, filter, map, tap } from 'rxjs'; +import { Observable, filter, map } from 'rxjs'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; From 821cb99f5be3100a895325be5bc9f53e0e9f914d Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 10 Oct 2023 11:22:36 +0200 Subject: [PATCH 50/56] [CST-10703] relocation of files --- .../external-log-in.methods-decorator.ts | 2 +- .../external-login-method-entry.component.ts | 0 .../confirm-email.component.html | 0 .../confirm-email.component.scss | 0 .../confirm-email.component.spec.ts | 20 +++++------ .../confirm-email/confirm-email.component.ts | 16 ++++----- .../confirmation-sent.component.html | 0 .../confirmation-sent.component.scss | 0 .../confirmation-sent.component.spec.ts | 2 +- .../confirmation-sent.component.ts | 0 .../provide-email.component.html | 0 .../provide-email.component.scss | 0 .../provide-email.component.spec.ts | 2 +- .../provide-email/provide-email.component.ts | 2 +- .../external-log-in.component.html | 0 .../external-log-in.component.scss | 0 .../external-log-in.component.spec.ts | 32 ++++++++++------- .../external-log-in.component.ts | 12 +++---- .../external-login-complete.module.ts | 35 +++++++++++++++++++ .../guards/registration-token.guard.spec.ts | 14 ++++---- .../guards/registration-token.guard.ts | 13 +++---- .../models/registration-data.mock.model.ts | 7 ++-- .../orcid-confirmation.component.html | 8 ++--- .../orcid-confirmation.component.scss | 0 .../orcid-confirmation.component.spec.ts | 10 +++--- .../orcid-confirmation.component.ts | 9 +++-- .../registration-data.resolver.spec.ts | 6 ++-- .../resolvers/registration-data.resolver.ts | 10 +++--- .../services/external-login.service.spec.ts | 14 ++++---- .../services/external-login.service.ts | 18 +++++----- ...-email-confirmation-page.component.spec.ts | 2 +- ...al-login-email-confirmation-page.module.ts | 4 +-- .../external-login-page-routing.module.ts | 4 +-- .../external-login-page.module.ts | 4 ++- ...review-account-info-page-routing.module.ts | 2 +- ...review-account-info-page.component.spec.ts | 2 +- ...l-login-review-account-info-page.module.ts | 3 +- .../review-account-info.component.html | 2 +- .../review-account-info.component.spec.ts | 4 +-- .../review-account-info.component.ts | 2 +- ...-menu-expandable-section.component.spec.ts | 2 +- src/app/shared/shared.module.ts | 10 ------ 42 files changed, 154 insertions(+), 119 deletions(-) rename src/app/{shared/external-log-in-complete => external-log-in-complete/decorators}/external-log-in.methods-decorator.ts (90%) rename src/app/{shared/external-log-in-complete => external-log-in-complete/decorators}/external-login-method-entry.component.ts (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts (88%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts (89%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts (95%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss (100%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts (96%) rename src/app/{shared => }/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts (96%) rename src/app/{shared => }/external-log-in-complete/external-log-in/external-log-in.component.html (100%) rename src/app/{shared => }/external-log-in-complete/external-log-in/external-log-in.component.scss (100%) rename src/app/{shared => }/external-log-in-complete/external-log-in/external-log-in.component.spec.ts (78%) rename src/app/{shared => }/external-log-in-complete/external-log-in/external-log-in.component.ts (89%) create mode 100644 src/app/external-log-in-complete/external-login-complete.module.ts rename src/app/{shared => }/external-log-in-complete/guards/registration-token.guard.spec.ts (81%) rename src/app/{shared => }/external-log-in-complete/guards/registration-token.guard.ts (68%) rename src/app/{shared => }/external-log-in-complete/models/registration-data.mock.model.ts (82%) rename src/app/{shared => }/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html (83%) rename src/app/{shared => }/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss (100%) rename src/app/{shared => }/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts (84%) rename src/app/{shared => }/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts (83%) rename src/app/{shared => }/external-log-in-complete/resolvers/registration-data.resolver.spec.ts (89%) rename src/app/{shared => }/external-log-in-complete/resolvers/registration-data.resolver.ts (76%) rename src/app/{shared => }/external-log-in-complete/services/external-login.service.spec.ts (84%) rename src/app/{shared => }/external-log-in-complete/services/external-login.service.ts (77%) diff --git a/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts b/src/app/external-log-in-complete/decorators/external-log-in.methods-decorator.ts similarity index 90% rename from src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts rename to src/app/external-log-in-complete/decorators/external-log-in.methods-decorator.ts index ce90aea0a3f..cfd3a42d40b 100644 --- a/src/app/shared/external-log-in-complete/external-log-in.methods-decorator.ts +++ b/src/app/external-log-in-complete/decorators/external-log-in.methods-decorator.ts @@ -1,4 +1,4 @@ -import { AuthRegistrationType } from 'src/app/core/auth/models/auth.registration-type'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; /** * Map to store the external login confirmation component for the given auth method type diff --git a/src/app/shared/external-log-in-complete/external-login-method-entry.component.ts b/src/app/external-log-in-complete/decorators/external-login-method-entry.component.ts similarity index 100% rename from src/app/shared/external-log-in-complete/external-login-method-entry.component.ts rename to src/app/external-log-in-complete/decorators/external-login-method-entry.component.ts diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html rename to src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss rename to src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts similarity index 88% rename from src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts rename to src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts index e250fc41e03..eb398597aac 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -8,20 +8,18 @@ import { TranslateModule, TranslateService, } from '@ngx-translate/core'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { - createSuccessfulRemoteDataObject$, -} from '../../../../shared/remote-data.utils'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { of } from 'rxjs'; -import { Registration } from '../../../../core/shared/registration.model'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +import { AuthService } from '../../../core/auth/auth.service'; +import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; +import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { HardRedirectService } from '../../../core/services/hard-redirect.service'; +import { Registration } from '../../../core/shared/registration.model'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts similarity index 89% rename from src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts rename to src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts index 73bc5adfa2c..02692afa7a3 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,17 +1,17 @@ import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; -import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../core/shared/operators'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { hasNoValue, hasValue } from '../../../../shared/empty.util'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; -import { AuthService } from '../../../../core/auth/auth.service'; import { Subscription, combineLatest, take } from 'rxjs'; -import { Registration } from '../../../../core/shared/registration.model'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +import { AuthService } from '../../../core/auth/auth.service'; +import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { HardRedirectService } from '../../../core/services/hard-redirect.service'; +import { getRemoteDataPayload, getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { Registration } from '../../../core/shared/registration.model'; +import { hasNoValue, hasValue } from '../../../shared/empty.util'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; @Component({ selector: 'ds-confirm-email', diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html b/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html rename to src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss b/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss rename to src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts b/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts similarity index 95% rename from src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts rename to src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts index 3de88d9ba26..791e0e58e46 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts +++ b/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts @@ -4,8 +4,8 @@ import { ConfirmationSentComponent } from './confirmation-sent.component'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; import { TranslateService, TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { of } from 'rxjs'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; describe('ConfirmationSentComponent', () => { let component: ConfirmationSentComponent; diff --git a/src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts b/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts rename to src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html rename to src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss similarity index 100% rename from src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss rename to src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts similarity index 96% rename from src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts rename to src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts index 3ccb0a189a5..a346bfa930f 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts +++ b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts @@ -4,9 +4,9 @@ import { ProvideEmailComponent } from './provide-email.component'; import { FormBuilder } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; describe('ProvideEmailComponent', () => { let component: ProvideEmailComponent; diff --git a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts similarity index 96% rename from src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts rename to src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts index a63486dea60..52687b62010 100644 --- a/src/app/shared/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts @@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/c import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { Subscription } from 'rxjs'; -import { hasValue } from '../../../../shared/empty.util'; +import { hasValue } from '../../../shared/empty.util'; @Component({ selector: 'ds-provide-email', diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/external-log-in-complete/external-log-in/external-log-in.component.html similarity index 100% rename from src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.html rename to src/app/external-log-in-complete/external-log-in/external-log-in.component.html diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.scss b/src/app/external-log-in-complete/external-log-in/external-log-in.component.scss similarity index 100% rename from src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.scss rename to src/app/external-log-in-complete/external-log-in/external-log-in.component.scss diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/external-log-in-complete/external-log-in/external-log-in.component.spec.ts similarity index 78% rename from src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts rename to src/app/external-log-in-complete/external-log-in/external-log-in.component.spec.ts index 9e3b74f2bfe..4f444e920df 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.spec.ts +++ b/src/app/external-log-in-complete/external-log-in/external-log-in.component.spec.ts @@ -3,17 +3,19 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLogInComponent } from './external-log-in.component'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; -import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; -import { EventEmitter, Injector } from '@angular/core'; +import { EventEmitter, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { of as observableOf } from 'rxjs'; import { FormBuilder } from '@angular/forms'; -import { AuthService } from '../../../core/auth/auth.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { AuthServiceMock } from '../../mocks/auth.service.mock'; -import { MetadataValue } from '../../../core/shared/metadata.models'; -import { Registration } from '../../../core/shared/registration.model'; -import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; +import { AuthService } from '../../core/auth/auth.service'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; +import { MetadataValue } from '../../core/shared/metadata.models'; +import { Registration } from '../../core/shared/registration.model'; +import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { BrowserOnlyMockPipe } from '../../shared/testing/browser-only-mock.pipe'; +import { OrcidConfirmationComponent } from '../registration-types/orcid-confirmation/orcid-confirmation.component'; describe('ExternalLogInComponent', () => { let component: ExternalLogInComponent; @@ -40,7 +42,9 @@ describe('ExternalLogInComponent', () => { }; const translateServiceStub = { get: () => observableOf('Info Text'), - instant: (key: any) => 'Info Text', + instant(key) { + return 'Info Text'; + }, onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), onDefaultLangChange: new EventEmitter() @@ -48,7 +52,7 @@ describe('ExternalLogInComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ExternalLogInComponent], + declarations: [ExternalLogInComponent, BrowserOnlyMockPipe, ], providers: [ { provide: TranslateService, useValue: translateServiceStub }, { provide: Injector, useValue: {} }, @@ -64,12 +68,18 @@ describe('ExternalLogInComponent', () => { useClass: TranslateLoaderMock } }), - ] + ], + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); beforeEach(() => { + TestBed.overrideComponent(OrcidConfirmationComponent, { + set: { + template: '
Mocked OrcidConfirmationComponent
', + }, + }); fixture = TestBed.createComponent(ExternalLogInComponent); component = fixture.componentInstance; component.registrationData = Object.assign(new Registration, registrationDataMock); @@ -111,5 +121,3 @@ describe('ExternalLogInComponent', () => { expect(infoText.nativeElement.innerHTML).toContain('Info Text'); }); }); - - diff --git a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/external-log-in-complete/external-log-in/external-log-in.component.ts similarity index 89% rename from src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts rename to src/app/external-log-in-complete/external-log-in/external-log-in.component.ts index d167a0c5d08..3917567e414 100644 --- a/src/app/shared/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/external-log-in-complete/external-log-in/external-log-in.component.ts @@ -5,13 +5,13 @@ import { Input, Injector, } from '@angular/core'; -import { getExternalLoginConfirmationType } from '../external-log-in.methods-decorator'; -import { hasValue } from '../../empty.util'; +import { NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { AuthService } from '../../../core/auth/auth.service'; -import { Registration } from '../../../core/shared/registration.model'; -import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; +import { AuthService } from '../../core/auth/auth.service'; +import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; +import { Registration } from '../../core/shared/registration.model'; +import { hasValue } from '../../shared/empty.util'; +import { getExternalLoginConfirmationType } from '../decorators/external-log-in.methods-decorator'; @Component({ selector: 'ds-external-log-in', diff --git a/src/app/external-log-in-complete/external-login-complete.module.ts b/src/app/external-log-in-complete/external-login-complete.module.ts new file mode 100644 index 00000000000..d68f13b0eca --- /dev/null +++ b/src/app/external-log-in-complete/external-login-complete.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ConfirmEmailComponent } from './email-confirmation/confirm-email/confirm-email.component'; +import { ConfirmationSentComponent } from './email-confirmation/confirmation-sent/confirmation-sent.component'; +import { ProvideEmailComponent } from './email-confirmation/provide-email/provide-email.component'; +import { ExternalLogInComponent } from './external-log-in/external-log-in.component'; +import { OrcidConfirmationComponent } from './registration-types/orcid-confirmation/orcid-confirmation.component'; +import { SharedModule } from '../shared/shared.module'; + +const COMPONENTS = [ + ExternalLogInComponent, + ProvideEmailComponent, + ConfirmEmailComponent, + ConfirmationSentComponent, +]; + +const ENTRY_COMPONENTS = [OrcidConfirmationComponent]; + +@NgModule({ + declarations: [...COMPONENTS, ...ENTRY_COMPONENTS], + imports: [CommonModule, SharedModule], + exports: [...COMPONENTS, ...ENTRY_COMPONENTS], +}) +export class ExternalLoginCompleteModule { + /** + * NOTE: this method allows to resolve issue with components that using a custom decorator + * which are not loaded during SSR otherwise + */ + static withEntryComponents() { + return { + ngModule: ExternalLoginCompleteModule, + providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), + }; + } +} diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.spec.ts b/src/app/external-log-in-complete/guards/registration-token.guard.spec.ts similarity index 81% rename from src/app/shared/external-log-in-complete/guards/registration-token.guard.spec.ts rename to src/app/external-log-in-complete/guards/registration-token.guard.spec.ts index f62769add66..155e27c4c53 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.spec.ts +++ b/src/app/external-log-in-complete/guards/registration-token.guard.spec.ts @@ -2,12 +2,13 @@ import { TestBed } from '@angular/core/testing'; import { RegistrationTokenGuard } from './registration-token.guard'; import { ActivatedRoute, convertToParamMap, Params, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { AuthService } from '../../../core/auth/auth.service'; -import { RouterMock } from '../../../shared/mocks/router.mock'; -import { Registration } from '../../../core/shared/registration.model'; -import { EPerson } from '../../../core/eperson/models/eperson.model'; -import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { AuthService } from '../../core/auth/auth.service'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { Registration } from '../../core/shared/registration.model'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { AuthRegistrationType } from 'src/app/core/auth/models/auth.registration-type'; describe('RegistrationTokenGuard', () => { let guard: RegistrationTokenGuard; @@ -16,6 +17,7 @@ describe('RegistrationTokenGuard', () => { { email: 'test@email.org', token: 'test-token', + registrationType: AuthRegistrationType.Orcid, }); const epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', { searchRegistrationByToken: createSuccessfulRemoteDataObject$(registrationWithGroups) diff --git a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts b/src/app/external-log-in-complete/guards/registration-token.guard.ts similarity index 68% rename from src/app/shared/external-log-in-complete/guards/registration-token.guard.ts rename to src/app/external-log-in-complete/guards/registration-token.guard.ts index d565dfbd91a..8f0db7b63f4 100644 --- a/src/app/shared/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/external-log-in-complete/guards/registration-token.guard.ts @@ -6,11 +6,12 @@ import { RouterStateSnapshot, } from '@angular/router'; import { Observable, map, of } from 'rxjs'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { RemoteData } from '../../../core/data/remote-data'; -import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { Registration } from '../../../core/shared/registration.model'; -import { hasValue } from '../../empty.util'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Registration } from '../../core/shared/registration.model'; +import { hasValue } from '../../shared/empty.util'; +import { AuthRegistrationType } from 'src/app/core/auth/models/auth.registration-type'; @Injectable({ providedIn: 'root', @@ -38,7 +39,7 @@ export class RegistrationTokenGuard implements CanActivate { getFirstCompletedRemoteData(), map( (data: RemoteData) => { - if (data.hasSucceeded && hasValue(data)) { + if (data.hasSucceeded && hasValue(data.payload) && !data.payload.registrationType.includes(AuthRegistrationType.Validation)) { return true; } else { this.router.navigate(['/404']); diff --git a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/external-log-in-complete/models/registration-data.mock.model.ts similarity index 82% rename from src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts rename to src/app/external-log-in-complete/models/registration-data.mock.model.ts index 6dc1eb28632..4c288d17483 100644 --- a/src/app/shared/external-log-in-complete/models/registration-data.mock.model.ts +++ b/src/app/external-log-in-complete/models/registration-data.mock.model.ts @@ -1,6 +1,7 @@ -import { AuthMethodType } from '../../../core/auth/models/auth.method-type'; -import { MetadataValue } from '../../../core/shared/metadata.models'; -import { Registration } from '../../../core/shared/registration.model'; +import { AuthMethodType } from '../../core/auth/models/auth.method-type'; +import { MetadataValue } from '../../core/shared/metadata.models'; +import { Registration } from '../../core/shared/registration.model'; + export const mockRegistrationDataModel: Registration = Object.assign( new Registration(), diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html b/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html similarity index 83% rename from src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html rename to src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html index 4c81c7fdb15..df5bb454dde 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html +++ b/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html @@ -1,7 +1,7 @@ - - {{ registratioData.registrationType }} + - - { let component: OrcidConfirmationComponent; let fixture: ComponentFixture; - let model: Registration; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ OrcidConfirmationComponent, - BrowserOnlyMockPipe, + BrowserOnlyMockPipe ], providers: [ FormBuilder, diff --git a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts b/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts similarity index 83% rename from src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts rename to src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts index 3cd3b875dc0..4cba863bef3 100644 --- a/src/app/shared/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts +++ b/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit, ChangeDetectionStrategy, Inject } from '@angular/core'; -import { renderExternalLoginConfirmationFor } from '../../external-log-in.methods-decorator'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { ExternalLoginMethodEntryComponent } from '../../external-login-method-entry.component'; -import { Registration } from '../../../../core/shared/registration.model'; -import { AuthRegistrationType } from '../../../../core/auth/models/auth.registration-type'; - +import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; +import { Registration } from '../../../core/shared/registration.model'; +import { renderExternalLoginConfirmationFor } from '../../decorators/external-log-in.methods-decorator'; +import { ExternalLoginMethodEntryComponent } from '../../decorators/external-login-method-entry.component'; @Component({ selector: 'ds-orcid-confirmation', templateUrl: './orcid-confirmation.component.html', diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts b/src/app/external-log-in-complete/resolvers/registration-data.resolver.spec.ts similarity index 89% rename from src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts rename to src/app/external-log-in-complete/resolvers/registration-data.resolver.spec.ts index 0d1f415a3b4..82641a6a94d 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.spec.ts +++ b/src/app/external-log-in-complete/resolvers/registration-data.resolver.spec.ts @@ -1,10 +1,10 @@ import { TestBed } from '@angular/core/testing'; import { RegistrationDataResolver } from './registration-data.resolver'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { Registration } from '../../../core/shared/registration.model'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { Registration } from '../../core/shared/registration.model'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('RegistrationDataResolver', () => { let resolver: RegistrationDataResolver; diff --git a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts b/src/app/external-log-in-complete/resolvers/registration-data.resolver.ts similarity index 76% rename from src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts rename to src/app/external-log-in-complete/resolvers/registration-data.resolver.ts index 6a9b6b8b31b..49667ab77a0 100644 --- a/src/app/shared/external-log-in-complete/resolvers/registration-data.resolver.ts +++ b/src/app/external-log-in-complete/resolvers/registration-data.resolver.ts @@ -5,11 +5,11 @@ import { ActivatedRouteSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { hasValue } from '../../empty.util'; -import { Registration } from '../../../core/shared/registration.model'; -import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { RemoteData } from '../../../core/data/remote-data'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Registration } from '../../core/shared/registration.model'; +import { hasValue } from '../../shared/empty.util'; @Injectable({ providedIn: 'root', diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts b/src/app/external-log-in-complete/services/external-login.service.spec.ts similarity index 84% rename from src/app/shared/external-log-in-complete/services/external-login.service.spec.ts rename to src/app/external-log-in-complete/services/external-login.service.spec.ts index ce0dcb4e269..896fec12a8b 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.spec.ts +++ b/src/app/external-log-in-complete/services/external-login.service.spec.ts @@ -3,16 +3,16 @@ import { TestBed } from '@angular/core/testing'; import { ExternalLoginService } from './external-login.service'; import { TranslateService } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { RemoteData } from '../../../core/data/remote-data'; -import { Registration } from '../../../core/shared/registration.model'; -import { NotificationsService } from '../../notifications/notifications.service'; -import { RouterMock } from '../../mocks/router.mock'; -import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; -import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { Registration } from '../../core/shared/registration.model'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; describe('ExternalLoginService', () => { let service: ExternalLoginService; diff --git a/src/app/shared/external-log-in-complete/services/external-login.service.ts b/src/app/external-log-in-complete/services/external-login.service.ts similarity index 77% rename from src/app/shared/external-log-in-complete/services/external-login.service.ts rename to src/app/external-log-in-complete/services/external-login.service.ts index 03c671b570a..70ab98832ff 100644 --- a/src/app/shared/external-log-in-complete/services/external-login.service.ts +++ b/src/app/external-log-in-complete/services/external-login.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Observable, filter, map, tap } from 'rxjs'; -import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; -import { RemoteData } from '../../../core/data/remote-data'; -import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { NotificationsService } from '../../notifications/notifications.service'; +import { Observable, filter, map } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; -import { NoContent } from '../../../core/shared/NoContent.model'; -import { AuthMethod } from 'src/app/core/auth/models/auth.method'; -import { getAuthenticationMethods } from 'src/app/core/auth/selectors'; +import { AuthMethod } from '../../core/auth/models/auth.method'; +import { getAuthenticationMethods } from '../../core/auth/selectors'; import { Store, select } from '@ngrx/store'; -import { CoreState } from 'src/app/core/core-state.model'; +import { CoreState } from '../../core/core-state.model'; +import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { NoContent } from '../../core/shared/NoContent.model'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Injectable({ providedIn: 'root' diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts index 89a96222d13..270dc60125e 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts @@ -1,9 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; -import { ConfirmationSentComponent } from '../shared/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; +import { ConfirmationSentComponent } from '../external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; describe('ExternalLoginEmailConfirmationPageComponent', () => { let component: ExternalLoginEmailConfirmationPageComponent; diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts index 36684f2f367..d62da55dbd7 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { ExternalLoginEmailConfirmationPageRoutingModule } from './external-login-email-confirmation-page-routing.module'; import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; -import { SharedModule } from '../shared/shared.module'; +import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; @NgModule({ @@ -13,7 +13,7 @@ import { SharedModule } from '../shared/shared.module'; imports: [ CommonModule, ExternalLoginEmailConfirmationPageRoutingModule, - SharedModule + ExternalLoginCompleteModule, ] }) export class ExternalLoginEmailConfirmationPageModule { } diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index 21bbb7ee920..641809740d3 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -1,8 +1,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; -import { RegistrationTokenGuard } from '../shared/external-log-in-complete/guards/registration-token.guard'; -import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; +import { RegistrationDataResolver } from '../external-log-in-complete/resolvers/registration-data.resolver'; +import { RegistrationTokenGuard } from '../external-log-in-complete/guards/registration-token.guard'; const routes: Routes = [ { diff --git a/src/app/external-login-page/external-login-page.module.ts b/src/app/external-login-page/external-login-page.module.ts index 697ae7e1a8e..ac68dc5c309 100644 --- a/src/app/external-login-page/external-login-page.module.ts +++ b/src/app/external-login-page/external-login-page.module.ts @@ -5,6 +5,7 @@ import { ExternalLoginPageRoutingModule } from './external-login-page-routing.mo import { ExternalLoginPageComponent } from './external-login-page.component'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; import { SharedModule } from '../shared/shared.module'; +import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; const COMPONENTS = [ ExternalLoginPageComponent, @@ -18,7 +19,8 @@ const COMPONENTS = [ imports: [ CommonModule, ExternalLoginPageRoutingModule, - SharedModule + SharedModule, + ExternalLoginCompleteModule ] }) export class ExternalLoginPageModule { } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts index 1119c9fc49b..66e3e29d056 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts @@ -1,8 +1,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; -import { RegistrationDataResolver } from '../shared/external-log-in-complete/resolvers/registration-data.resolver'; import { ReviewAccountGuard } from './helpers/review-account.guard'; +import { RegistrationDataResolver } from '../external-log-in-complete/resolvers/registration-data.resolver'; const routes: Routes = [ { diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts index 4b136cbef7a..816cdb4e1ff 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; -import { mockRegistrationDataModel } from '../shared/external-log-in-complete/models/registration-data.mock.model'; +import { mockRegistrationDataModel } from '../external-log-in-complete/models/registration-data.mock.model'; describe('ExternalLoginReviewAccountInfoPageComponent', () => { let component: ExternalLoginReviewAccountInfoPageComponent; diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts b/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts index 368f988a829..13bad328d1f 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts +++ b/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts @@ -8,7 +8,7 @@ import { ThemedExternalLoginReviewAccountInfoPageComponent } from './themed-exte import { ReviewAccountInfoComponent } from './review-account-info/review-account-info.component'; import { UiSwitchModule } from 'ngx-ui-switch'; import { SharedModule } from '../shared/shared.module'; - +import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; @NgModule({ declarations: [ @@ -22,6 +22,7 @@ import { SharedModule } from '../shared/shared.module'; ExternalLoginReviewAccountInfoRoutingModule, SharedModule, UiSwitchModule, + ExternalLoginCompleteModule ] }) export class ExternalLoginReviewAccountInfoModule { } diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html index da5d943676f..fb4e1298be0 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html @@ -19,7 +19,7 @@

{{'external-login-validation.review-account-info.header' | translate}}

- +
{{ registrationData.registrationType | uppercase }}{{ registrationData.registrationType }} {{ registrationData.netId }} diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts index 63545370ab7..2bfbd582e4a 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts @@ -24,8 +24,8 @@ import { CompareValuesPipe } from '../helpers/compare-values.pipe'; import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; -import { ExternalLoginService } from '../../shared/external-log-in-complete/services/external-login.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { ExternalLoginService } from '../../external-log-in-complete/services/external-login.service'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; @@ -179,7 +179,7 @@ describe('ReviewAccountInfoComponent', () => { const registrationTypeElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child th'); const netIdElement: HTMLElement = fixture.nativeElement.querySelector('tbody tr:first-child td'); - expect(registrationTypeElement.textContent.trim()).toBe(registrationDataMock.registrationType.toUpperCase()); + expect(registrationTypeElement.textContent.trim()).toBe(registrationDataMock.registrationType); expect(netIdElement.textContent.trim()).toBe(registrationDataMock.netId); }); diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts index cca523ce4c2..79f9cac83c5 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts @@ -17,9 +17,9 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { Router } from '@angular/router'; import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; -import { ExternalLoginService } from '../../shared/external-log-in-complete/services/external-login.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; +import { ExternalLoginService } from '../../external-log-in-complete/services/external-login.service'; export interface ReviewAccountInfoData { label: string; diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.spec.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.spec.ts index 79ab35bd28c..7a72cc907cc 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.spec.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.spec.ts @@ -10,7 +10,7 @@ import { of as observableOf } from 'rxjs'; import { Component } from '@angular/core'; import { DsoEditMenuExpandableSectionComponent } from './dso-edit-menu-expandable-section.component'; import { By } from '@angular/platform-browser'; -import { MenuItemType } from 'src/app/shared/menu/menu-item-type.model'; +import { MenuItemType } from '../../../../shared/menu/menu-item-type.model'; describe('DsoEditMenuExpandableSectionComponent', () => { let component: DsoEditMenuExpandableSectionComponent; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 65bcd2dfa3c..c59d2bedaf2 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -332,11 +332,6 @@ import { EntityIconDirective } from './entity-icon/entity-icon.directive'; import { AdditionalMetadataComponent } from './object-list/search-result-list-element/additional-metadata/additional-metadata.component'; -import { ExternalLogInComponent } from './external-log-in-complete/external-log-in/external-log-in.component'; -import { OrcidConfirmationComponent } from './external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component'; -import { ProvideEmailComponent } from './external-log-in-complete/email-confirmation/provide-email/provide-email.component'; -import { ConfirmEmailComponent } from './external-log-in-complete/email-confirmation/confirm-email/confirm-email.component'; -import { ConfirmationSentComponent } from './external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; const MODULES = [ CommonModule, @@ -475,10 +470,6 @@ const COMPONENTS = [ ExportExcelSelectorComponent, ThemedBrowseMostElementsComponent, SearchChartBarHorizontalComponent, - ExternalLogInComponent, - ProvideEmailComponent, - ConfirmEmailComponent, - ConfirmationSentComponent, ]; const ENTRY_COMPONENTS = [ @@ -553,7 +544,6 @@ const ENTRY_COMPONENTS = [ SearchChartBarHorizontalComponent, RelationshipsListComponent, AdditionalMetadataComponent, - OrcidConfirmationComponent, ]; const PROVIDERS = [ From 2fc33b5ca2e0906efe8e2a130b4a7371df350757 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 Oct 2023 13:14:50 +0200 Subject: [PATCH 51/56] [CST-10703] Renamed components and modules --- src/app/app-routing.module.ts | 2 +- .../decorators/external-log-in.methods-decorator.ts | 0 .../external-login-method-entry.component.ts | 0 .../confirm-email/confirm-email.component.html | 0 .../confirm-email/confirm-email.component.scss | 0 .../confirm-email/confirm-email.component.spec.ts | 6 +----- .../confirm-email/confirm-email.component.ts | 6 +++--- .../confirmation-sent.component.html | 0 .../confirmation-sent.component.scss | 0 .../confirmation-sent.component.spec.ts | 2 +- .../confirmation-sent/confirmation-sent.component.ts | 2 +- .../provide-email/provide-email.component.html | 0 .../provide-email/provide-email.component.scss | 0 .../provide-email/provide-email.component.spec.ts | 0 .../provide-email/provide-email.component.ts | 2 +- .../external-log-in/external-log-in.component.html | 0 .../external-log-in/external-log-in.component.scss | 0 .../external-log-in.component.spec.ts | 0 .../external-log-in/external-log-in.component.ts | 10 ++-------- .../external-login.module.ts} | 4 ++-- .../guards/registration-token.guard.spec.ts | 0 .../guards/registration-token.guard.ts | 9 ++------- .../models/registration-data.mock.model.ts | 0 .../orcid-confirmation.component.html | 0 .../orcid-confirmation.component.scss | 0 .../orcid-confirmation.component.spec.ts | 0 .../orcid-confirmation.component.ts | 3 ++- .../resolvers/registration-data.resolver.spec.ts | 0 .../resolvers/registration-data.resolver.ts | 6 +----- .../services/external-login.service.spec.ts | 0 .../services/external-login.service.ts | 4 ++-- ...l-login-email-confirmation-page.component.spec.ts | 4 +++- .../external-login-email-confirmation-page.module.ts | 8 +++++--- .../external-login-page-routing.module.ts | 4 ++-- .../external-login-page.module.ts | 4 ++-- ...-login-review-account-info-page-routing.module.ts | 2 +- ...nal-login-review-account-info-page.component.html | 0 ...nal-login-review-account-info-page.component.scss | 0 ...-login-review-account-info-page.component.spec.ts | 2 +- ...ernal-login-review-account-info-page.component.ts | 2 +- ...external-login-review-account-info-page.module.ts | 8 +++++--- .../helpers/compare-values.pipe.ts | 0 .../helpers/review-account.guard.spec.ts | 0 .../helpers/review-account.guard.ts | 9 ++------- .../review-account-info.component.html | 0 .../review-account-info.component.scss | 0 .../review-account-info.component.spec.ts | 12 ++++-------- .../review-account-info.component.ts | 12 +++--------- ...ernal-login-review-account-info-page.component.ts | 0 49 files changed, 48 insertions(+), 75 deletions(-) rename src/app/{external-log-in-complete => external-log-in}/decorators/external-log-in.methods-decorator.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/decorators/external-login-method-entry.component.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirm-email/confirm-email.component.html (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirm-email/confirm-email.component.scss (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirm-email/confirm-email.component.spec.ts (98%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirm-email/confirm-email.component.ts (97%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirmation-sent/confirmation-sent.component.html (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirmation-sent/confirmation-sent.component.scss (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts (96%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/confirmation-sent/confirmation-sent.component.ts (80%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/provide-email/provide-email.component.html (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/provide-email/provide-email.component.scss (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/provide-email/provide-email.component.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/email-confirmation/provide-email/provide-email.component.ts (96%) rename src/app/{external-log-in-complete => external-log-in}/external-log-in/external-log-in.component.html (100%) rename src/app/{external-log-in-complete => external-log-in}/external-log-in/external-log-in.component.scss (100%) rename src/app/{external-log-in-complete => external-log-in}/external-log-in/external-log-in.component.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/external-log-in/external-log-in.component.ts (96%) rename src/app/{external-log-in-complete/external-login-complete.module.ts => external-log-in/external-login.module.ts} (93%) rename src/app/{external-log-in-complete => external-log-in}/guards/registration-token.guard.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/guards/registration-token.guard.ts (91%) rename src/app/{external-log-in-complete => external-log-in}/models/registration-data.mock.model.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/registration-types/orcid-confirmation/orcid-confirmation.component.html (100%) rename src/app/{external-log-in-complete => external-log-in}/registration-types/orcid-confirmation/orcid-confirmation.component.scss (100%) rename src/app/{external-log-in-complete => external-log-in}/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/registration-types/orcid-confirmation/orcid-confirmation.component.ts (97%) rename src/app/{external-log-in-complete => external-log-in}/resolvers/registration-data.resolver.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/resolvers/registration-data.resolver.ts (93%) rename src/app/{external-log-in-complete => external-log-in}/services/external-login.service.spec.ts (100%) rename src/app/{external-log-in-complete => external-log-in}/services/external-login.service.ts (96%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page-routing.module.ts (85%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page.component.html (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page.component.scss (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page.component.spec.ts (94%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page.component.ts (96%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/external-login-review-account-info-page.module.ts (78%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/helpers/compare-values.pipe.ts (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/helpers/review-account.guard.spec.ts (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/helpers/review-account.guard.ts (92%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/review-account-info/review-account-info.component.html (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/review-account-info/review-account-info.component.scss (100%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/review-account-info/review-account-info.component.spec.ts (96%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/review-account-info/review-account-info.component.ts (97%) rename src/app/{external-login-review-account-info => external-login-review-account-info-page}/themed-external-login-review-account-info-page.component.ts (100%) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index a652eadde9e..513628fc413 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -178,7 +178,7 @@ import { RedirectService } from './redirect/redirect.service'; }, { path: 'review-account/:token', - loadChildren: () => import('./external-login-review-account-info/external-login-review-account-info-page.module') + loadChildren: () => import('./external-login-review-account-info-page/external-login-review-account-info-page.module') .then((m) => m.ExternalLoginReviewAccountInfoModule) }, { diff --git a/src/app/external-log-in-complete/decorators/external-log-in.methods-decorator.ts b/src/app/external-log-in/decorators/external-log-in.methods-decorator.ts similarity index 100% rename from src/app/external-log-in-complete/decorators/external-log-in.methods-decorator.ts rename to src/app/external-log-in/decorators/external-log-in.methods-decorator.ts diff --git a/src/app/external-log-in-complete/decorators/external-login-method-entry.component.ts b/src/app/external-log-in/decorators/external-login-method-entry.component.ts similarity index 100% rename from src/app/external-log-in-complete/decorators/external-login-method-entry.component.ts rename to src/app/external-log-in/decorators/external-login-method-entry.component.ts diff --git a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.html rename to src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html diff --git a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.scss similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.scss rename to src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.scss diff --git a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts similarity index 98% rename from src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts rename to src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts index eb398597aac..7c0e7f8d926 100644 --- a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -3,11 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmEmailComponent } from './confirm-email.component'; import { FormBuilder } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import { - TranslateLoader, - TranslateModule, - TranslateService, -} from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateService, } from '@ngx-translate/core'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalLoginService } from '../../services/external-login.service'; import { of } from 'rxjs'; diff --git a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts similarity index 97% rename from src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts rename to src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts index 02692afa7a3..56e28333b62 100644 --- a/src/app/external-log-in-complete/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,14 +1,14 @@ -import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { TranslateService } from '@ngx-translate/core'; import isEqual from 'lodash/isEqual'; -import { Subscription, combineLatest, take } from 'rxjs'; +import { combineLatest, Subscription, take } from 'rxjs'; import { AuthService } from '../../../core/auth/auth.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPerson } from '../../../core/eperson/models/eperson.model'; import { HardRedirectService } from '../../../core/services/hard-redirect.service'; -import { getRemoteDataPayload, getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; import { Registration } from '../../../core/shared/registration.model'; import { hasNoValue, hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; diff --git a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.html similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.html rename to src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.html diff --git a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.scss similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.scss rename to src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.scss diff --git a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts similarity index 96% rename from src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts rename to src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts index 791e0e58e46..da4e5416d54 100644 --- a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmationSentComponent } from './confirmation-sent.component'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core'; -import { TranslateService, TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; diff --git a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts similarity index 80% rename from src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts rename to src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts index 78a0ef81fed..2f82991c0d4 100644 --- a/src/app/external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component.ts +++ b/src/app/external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'ds-confirmation-sent', diff --git a/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.html rename to src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html diff --git a/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.scss similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.scss rename to src/app/external-log-in/email-confirmation/provide-email/provide-email.component.scss diff --git a/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts similarity index 100% rename from src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.spec.ts rename to src/app/external-log-in/email-confirmation/provide-email/provide-email.component.spec.ts diff --git a/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts similarity index 96% rename from src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts rename to src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts index 52687b62010..4e3e220eceb 100644 --- a/src/app/external-log-in-complete/email-confirmation/provide-email/provide-email.component.ts +++ b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { Subscription } from 'rxjs'; diff --git a/src/app/external-log-in-complete/external-log-in/external-log-in.component.html b/src/app/external-log-in/external-log-in/external-log-in.component.html similarity index 100% rename from src/app/external-log-in-complete/external-log-in/external-log-in.component.html rename to src/app/external-log-in/external-log-in/external-log-in.component.html diff --git a/src/app/external-log-in-complete/external-log-in/external-log-in.component.scss b/src/app/external-log-in/external-log-in/external-log-in.component.scss similarity index 100% rename from src/app/external-log-in-complete/external-log-in/external-log-in.component.scss rename to src/app/external-log-in/external-log-in/external-log-in.component.scss diff --git a/src/app/external-log-in-complete/external-log-in/external-log-in.component.spec.ts b/src/app/external-log-in/external-log-in/external-log-in.component.spec.ts similarity index 100% rename from src/app/external-log-in-complete/external-log-in/external-log-in.component.spec.ts rename to src/app/external-log-in/external-log-in/external-log-in.component.spec.ts diff --git a/src/app/external-log-in-complete/external-log-in/external-log-in.component.ts b/src/app/external-log-in/external-log-in/external-log-in.component.ts similarity index 96% rename from src/app/external-log-in-complete/external-log-in/external-log-in.component.ts rename to src/app/external-log-in/external-log-in/external-log-in.component.ts index 3917567e414..60d91d2aee3 100644 --- a/src/app/external-log-in-complete/external-log-in/external-log-in.component.ts +++ b/src/app/external-log-in/external-log-in/external-log-in.component.ts @@ -1,11 +1,5 @@ -import { - Component, - OnInit, - ChangeDetectionStrategy, - Input, - Injector, -} from '@angular/core'; -import { NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ChangeDetectionStrategy, Component, Injector, Input, OnInit, } from '@angular/core'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '../../core/auth/auth.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; diff --git a/src/app/external-log-in-complete/external-login-complete.module.ts b/src/app/external-log-in/external-login.module.ts similarity index 93% rename from src/app/external-log-in-complete/external-login-complete.module.ts rename to src/app/external-log-in/external-login.module.ts index d68f13b0eca..b0d75aa7303 100644 --- a/src/app/external-log-in-complete/external-login-complete.module.ts +++ b/src/app/external-log-in/external-login.module.ts @@ -21,14 +21,14 @@ const ENTRY_COMPONENTS = [OrcidConfirmationComponent]; imports: [CommonModule, SharedModule], exports: [...COMPONENTS, ...ENTRY_COMPONENTS], }) -export class ExternalLoginCompleteModule { +export class ExternalLoginModule { /** * NOTE: this method allows to resolve issue with components that using a custom decorator * which are not loaded during SSR otherwise */ static withEntryComponents() { return { - ngModule: ExternalLoginCompleteModule, + ngModule: ExternalLoginModule, providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), }; } diff --git a/src/app/external-log-in-complete/guards/registration-token.guard.spec.ts b/src/app/external-log-in/guards/registration-token.guard.spec.ts similarity index 100% rename from src/app/external-log-in-complete/guards/registration-token.guard.spec.ts rename to src/app/external-log-in/guards/registration-token.guard.spec.ts diff --git a/src/app/external-log-in-complete/guards/registration-token.guard.ts b/src/app/external-log-in/guards/registration-token.guard.ts similarity index 91% rename from src/app/external-log-in-complete/guards/registration-token.guard.ts rename to src/app/external-log-in/guards/registration-token.guard.ts index 8f0db7b63f4..31e38143a3b 100644 --- a/src/app/external-log-in-complete/guards/registration-token.guard.ts +++ b/src/app/external-log-in/guards/registration-token.guard.ts @@ -1,11 +1,6 @@ import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable, map, of } from 'rxjs'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, } from '@angular/router'; +import { map, Observable, of } from 'rxjs'; import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; import { RemoteData } from '../../core/data/remote-data'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; diff --git a/src/app/external-log-in-complete/models/registration-data.mock.model.ts b/src/app/external-log-in/models/registration-data.mock.model.ts similarity index 100% rename from src/app/external-log-in-complete/models/registration-data.mock.model.ts rename to src/app/external-log-in/models/registration-data.mock.model.ts diff --git a/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html similarity index 100% rename from src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.html rename to src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.html diff --git a/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.scss similarity index 100% rename from src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.scss rename to src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.scss diff --git a/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts similarity index 100% rename from src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts rename to src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.spec.ts diff --git a/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.ts similarity index 97% rename from src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts rename to src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.ts index 4cba863bef3..52973e8267b 100644 --- a/src/app/external-log-in-complete/registration-types/orcid-confirmation/orcid-confirmation.component.ts +++ b/src/app/external-log-in/registration-types/orcid-confirmation/orcid-confirmation.component.ts @@ -1,9 +1,10 @@ -import { Component, OnInit, ChangeDetectionStrategy, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { AuthRegistrationType } from '../../../core/auth/models/auth.registration-type'; import { Registration } from '../../../core/shared/registration.model'; import { renderExternalLoginConfirmationFor } from '../../decorators/external-log-in.methods-decorator'; import { ExternalLoginMethodEntryComponent } from '../../decorators/external-login-method-entry.component'; + @Component({ selector: 'ds-orcid-confirmation', templateUrl: './orcid-confirmation.component.html', diff --git a/src/app/external-log-in-complete/resolvers/registration-data.resolver.spec.ts b/src/app/external-log-in/resolvers/registration-data.resolver.spec.ts similarity index 100% rename from src/app/external-log-in-complete/resolvers/registration-data.resolver.spec.ts rename to src/app/external-log-in/resolvers/registration-data.resolver.spec.ts diff --git a/src/app/external-log-in-complete/resolvers/registration-data.resolver.ts b/src/app/external-log-in/resolvers/registration-data.resolver.ts similarity index 93% rename from src/app/external-log-in-complete/resolvers/registration-data.resolver.ts rename to src/app/external-log-in/resolvers/registration-data.resolver.ts index 49667ab77a0..306d0d8f235 100644 --- a/src/app/external-log-in-complete/resolvers/registration-data.resolver.ts +++ b/src/app/external-log-in/resolvers/registration-data.resolver.ts @@ -1,9 +1,5 @@ import { Injectable } from '@angular/core'; -import { - Resolve, - RouterStateSnapshot, - ActivatedRouteSnapshot, -} from '@angular/router'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; import { RemoteData } from '../../core/data/remote-data'; diff --git a/src/app/external-log-in-complete/services/external-login.service.spec.ts b/src/app/external-log-in/services/external-login.service.spec.ts similarity index 100% rename from src/app/external-log-in-complete/services/external-login.service.spec.ts rename to src/app/external-log-in/services/external-login.service.spec.ts diff --git a/src/app/external-log-in-complete/services/external-login.service.ts b/src/app/external-log-in/services/external-login.service.ts similarity index 96% rename from src/app/external-log-in-complete/services/external-login.service.ts rename to src/app/external-log-in/services/external-login.service.ts index 70ab98832ff..8f3fce41514 100644 --- a/src/app/external-log-in-complete/services/external-login.service.ts +++ b/src/app/external-log-in/services/external-login.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Observable, filter, map } from 'rxjs'; +import { filter, map, Observable } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { AuthMethod } from '../../core/auth/models/auth.method'; import { getAuthenticationMethods } from '../../core/auth/selectors'; -import { Store, select } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { CoreState } from '../../core/core-state.model'; import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; import { RemoteData } from '../../core/data/remote-data'; diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts index 270dc60125e..60e03b3c51c 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.component.spec.ts @@ -3,7 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; -import { ConfirmationSentComponent } from '../external-log-in-complete/email-confirmation/confirmation-sent/confirmation-sent.component'; +import { + ConfirmationSentComponent +} from '../external-log-in/email-confirmation/confirmation-sent/confirmation-sent.component'; describe('ExternalLoginEmailConfirmationPageComponent', () => { let component: ExternalLoginEmailConfirmationPageComponent; diff --git a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts index d62da55dbd7..fe91160627e 100644 --- a/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts +++ b/src/app/external-login-email-confirmation-page/external-login-email-confirmation-page.module.ts @@ -1,9 +1,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ExternalLoginEmailConfirmationPageRoutingModule } from './external-login-email-confirmation-page-routing.module'; +import { + ExternalLoginEmailConfirmationPageRoutingModule +} from './external-login-email-confirmation-page-routing.module'; import { ExternalLoginEmailConfirmationPageComponent } from './external-login-email-confirmation-page.component'; -import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; +import { ExternalLoginModule } from '../external-log-in/external-login.module'; @NgModule({ @@ -13,7 +15,7 @@ import { ExternalLoginCompleteModule } from '../external-log-in-complete/externa imports: [ CommonModule, ExternalLoginEmailConfirmationPageRoutingModule, - ExternalLoginCompleteModule, + ExternalLoginModule, ] }) export class ExternalLoginEmailConfirmationPageModule { } diff --git a/src/app/external-login-page/external-login-page-routing.module.ts b/src/app/external-login-page/external-login-page-routing.module.ts index 641809740d3..b248115ddbb 100644 --- a/src/app/external-login-page/external-login-page-routing.module.ts +++ b/src/app/external-login-page/external-login-page-routing.module.ts @@ -1,8 +1,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; -import { RegistrationDataResolver } from '../external-log-in-complete/resolvers/registration-data.resolver'; -import { RegistrationTokenGuard } from '../external-log-in-complete/guards/registration-token.guard'; +import { RegistrationDataResolver } from '../external-log-in/resolvers/registration-data.resolver'; +import { RegistrationTokenGuard } from '../external-log-in/guards/registration-token.guard'; const routes: Routes = [ { diff --git a/src/app/external-login-page/external-login-page.module.ts b/src/app/external-login-page/external-login-page.module.ts index ac68dc5c309..a7808057629 100644 --- a/src/app/external-login-page/external-login-page.module.ts +++ b/src/app/external-login-page/external-login-page.module.ts @@ -5,7 +5,7 @@ import { ExternalLoginPageRoutingModule } from './external-login-page-routing.mo import { ExternalLoginPageComponent } from './external-login-page.component'; import { ThemedExternalLoginPageComponent } from './themed-external-login-page.component'; import { SharedModule } from '../shared/shared.module'; -import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; +import { ExternalLoginModule } from '../external-log-in/external-login.module'; const COMPONENTS = [ ExternalLoginPageComponent, @@ -20,7 +20,7 @@ const COMPONENTS = [ CommonModule, ExternalLoginPageRoutingModule, SharedModule, - ExternalLoginCompleteModule + ExternalLoginModule ] }) export class ExternalLoginPageModule { } diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts b/src/app/external-login-review-account-info-page/external-login-review-account-info-page-routing.module.ts similarity index 85% rename from src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page-routing.module.ts index 66e3e29d056..afe0249f076 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page-routing.module.ts +++ b/src/app/external-login-review-account-info-page/external-login-review-account-info-page-routing.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; import { ReviewAccountGuard } from './helpers/review-account.guard'; -import { RegistrationDataResolver } from '../external-log-in-complete/resolvers/registration-data.resolver'; +import { RegistrationDataResolver } from '../external-log-in/resolvers/registration-data.resolver'; const routes: Routes = [ { diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.html b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.html similarity index 100% rename from src/app/external-login-review-account-info/external-login-review-account-info-page.component.html rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.html diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.scss b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.scss similarity index 100% rename from src/app/external-login-review-account-info/external-login-review-account-info-page.component.scss rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.scss diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.spec.ts similarity index 94% rename from src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.spec.ts index 816cdb4e1ff..7ef0e1cac3d 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.spec.ts +++ b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; -import { mockRegistrationDataModel } from '../external-log-in-complete/models/registration-data.mock.model'; +import { mockRegistrationDataModel } from '../external-log-in/models/registration-data.mock.model'; describe('ExternalLoginReviewAccountInfoPageComponent', () => { let component: ExternalLoginReviewAccountInfoPageComponent; diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.ts similarity index 96% rename from src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.ts index a42c9e05cfd..6217d4c79c2 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.component.ts +++ b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { AlertType } from '../shared/alert/aletr-type'; -import { Observable, first, map, tap } from 'rxjs'; +import { first, map, Observable, tap } from 'rxjs'; import { ActivatedRoute } from '@angular/router'; import { hasNoValue } from '../shared/empty.util'; import { Registration } from '../core/shared/registration.model'; diff --git a/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.module.ts similarity index 78% rename from src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts rename to src/app/external-login-review-account-info-page/external-login-review-account-info-page.module.ts index 13bad328d1f..bcad6db4265 100644 --- a/src/app/external-login-review-account-info/external-login-review-account-info-page.module.ts +++ b/src/app/external-login-review-account-info-page/external-login-review-account-info-page.module.ts @@ -4,11 +4,13 @@ import { CommonModule } from '@angular/common'; import { ExternalLoginReviewAccountInfoRoutingModule } from './external-login-review-account-info-page-routing.module'; import { ExternalLoginReviewAccountInfoPageComponent } from './external-login-review-account-info-page.component'; import { CompareValuesPipe } from './helpers/compare-values.pipe'; -import { ThemedExternalLoginReviewAccountInfoPageComponent } from './themed-external-login-review-account-info-page.component'; +import { + ThemedExternalLoginReviewAccountInfoPageComponent +} from './themed-external-login-review-account-info-page.component'; import { ReviewAccountInfoComponent } from './review-account-info/review-account-info.component'; import { UiSwitchModule } from 'ngx-ui-switch'; import { SharedModule } from '../shared/shared.module'; -import { ExternalLoginCompleteModule } from '../external-log-in-complete/external-login-complete.module'; +import { ExternalLoginModule } from '../external-log-in/external-login.module'; @NgModule({ declarations: [ @@ -22,7 +24,7 @@ import { ExternalLoginCompleteModule } from '../external-log-in-complete/externa ExternalLoginReviewAccountInfoRoutingModule, SharedModule, UiSwitchModule, - ExternalLoginCompleteModule + ExternalLoginModule ] }) export class ExternalLoginReviewAccountInfoModule { } diff --git a/src/app/external-login-review-account-info/helpers/compare-values.pipe.ts b/src/app/external-login-review-account-info-page/helpers/compare-values.pipe.ts similarity index 100% rename from src/app/external-login-review-account-info/helpers/compare-values.pipe.ts rename to src/app/external-login-review-account-info-page/helpers/compare-values.pipe.ts diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts b/src/app/external-login-review-account-info-page/helpers/review-account.guard.spec.ts similarity index 100% rename from src/app/external-login-review-account-info/helpers/review-account.guard.spec.ts rename to src/app/external-login-review-account-info-page/helpers/review-account.guard.spec.ts diff --git a/src/app/external-login-review-account-info/helpers/review-account.guard.ts b/src/app/external-login-review-account-info-page/helpers/review-account.guard.ts similarity index 92% rename from src/app/external-login-review-account-info/helpers/review-account.guard.ts rename to src/app/external-login-review-account-info-page/helpers/review-account.guard.ts index d908483c152..7caf465550c 100644 --- a/src/app/external-login-review-account-info/helpers/review-account.guard.ts +++ b/src/app/external-login-review-account-info-page/helpers/review-account.guard.ts @@ -1,11 +1,6 @@ import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { Observable, catchError, mergeMap, of, tap } from 'rxjs'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, } from '@angular/router'; +import { catchError, mergeMap, Observable, of, tap } from 'rxjs'; import { AuthService } from '../../core/auth/auth.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; import { EpersonRegistrationService } from '../../core/data/eperson-registration.service'; diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.html b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.html similarity index 100% rename from src/app/external-login-review-account-info/review-account-info/review-account-info.component.html rename to src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.html diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.scss b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.scss similarity index 100% rename from src/app/external-login-review-account-info/review-account-info/review-account-info.component.scss rename to src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.scss diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts similarity index 96% rename from src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts rename to src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts index 2bfbd582e4a..9b4bd2b8242 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts @@ -1,15 +1,11 @@ -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ReviewAccountInfoComponent } from './review-account-info.component'; -import { - TranslateLoader, - TranslateModule, - TranslateService, -} from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule, TranslateService, } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { Observable, Subscription, of } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; @@ -25,7 +21,7 @@ import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; -import { ExternalLoginService } from '../../external-log-in-complete/services/external-login.service'; +import { ExternalLoginService } from '../../external-log-in/services/external-login.service'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; diff --git a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts similarity index 97% rename from src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts rename to src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts index 79f9cac83c5..aee858c339c 100644 --- a/src/app/external-login-review-account-info/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts @@ -1,13 +1,7 @@ -import { - Component, - ChangeDetectionStrategy, - OnInit, - Input, - OnDestroy, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; -import { Observable, Subscription, combineLatest, filter, from, map, switchMap, take, tap } from 'rxjs'; +import { combineLatest, filter, from, map, Observable, Subscription, switchMap, take, tap } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -19,7 +13,7 @@ import { Registration } from '../../core/shared/registration.model'; import { AuthService } from '../../core/auth/auth.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; -import { ExternalLoginService } from '../../external-log-in-complete/services/external-login.service'; +import { ExternalLoginService } from '../../external-log-in/services/external-login.service'; export interface ReviewAccountInfoData { label: string; diff --git a/src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts b/src/app/external-login-review-account-info-page/themed-external-login-review-account-info-page.component.ts similarity index 100% rename from src/app/external-login-review-account-info/themed-external-login-review-account-info-page.component.ts rename to src/app/external-login-review-account-info-page/themed-external-login-review-account-info-page.component.ts From bf7f6eaebcde8e7aac8e35abfd113e745db4c44c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 Oct 2023 13:44:13 +0200 Subject: [PATCH 52/56] [CST-10703] change getExternalServerRedirectUrl in order to have origin as param --- src/app/core/auth/auth.service.ts | 5 +++-- .../confirm-email/confirm-email.component.spec.ts | 5 ++++- .../confirm-email/confirm-email.component.ts | 10 ++++++++-- .../review-account-info.component.spec.ts | 3 +++ .../review-account-info.component.ts | 10 ++++++++-- .../log-in-external-provider.component.ts | 6 +++++- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 4e089fc6347..128b03bd4d0 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -526,12 +526,13 @@ export class AuthService { /** * Returns the external server redirect URL. + * @param origin - The origin route. * @param redirectRoute - The redirect route. * @param location - The location. * @returns The external server redirect URL. */ - getExternalServerRedirectUrl(redirectRoute: string, location: string): string { - const correctRedirectUrl = new URLCombiner(this._window.nativeWindow.origin, redirectRoute).toString(); + getExternalServerRedirectUrl(origin: string, redirectRoute: string, location: string): string { + const correctRedirectUrl = new URLCombiner(origin, redirectRoute).toString(); let externalServerUrl = location; const myRegexp = /\?redirectUrl=(.*)/g; diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts index 7c0e7f8d926..1c9a876b027 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.spec.ts @@ -16,6 +16,8 @@ import { Registration } from '../../../core/shared/registration.model'; import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { NativeWindowService } from '../../../core/services/window.service'; +import { MockWindow, NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; describe('ConfirmEmailComponent', () => { let component: ConfirmEmailComponent; @@ -52,6 +54,7 @@ describe('ConfirmEmailComponent', () => { declarations: [ConfirmEmailComponent], providers: [ FormBuilder, + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, { provide: ExternalLoginService, useValue: externalLoginServiceSpy }, { provide: EPersonDataService, useValue: epersonDataServiceSpy }, { provide: NotificationsService, useValue: notificationServiceSpy }, @@ -138,7 +141,7 @@ describe('ConfirmEmailComponent', () => { expect(externalLoginServiceSpy.getExternalAuthLocation).toHaveBeenCalledWith(AuthMethodType.Orcid); expect(authServiceSpy.getRedirectUrl).toHaveBeenCalled(); expect(authServiceSpy.setRedirectUrl).toHaveBeenCalledWith('/profile'); - expect(authServiceSpy.getExternalServerRedirectUrl).toHaveBeenCalledWith('/test-redirect', 'test-location'); + expect(authServiceSpy.getExternalServerRedirectUrl).toHaveBeenCalledWith(MockWindow.origin,'/test-redirect', 'test-location'); expect(hardRedirectService.redirect).toHaveBeenCalledWith('test-external-url'); }); }); diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts index 56e28333b62..eaecf71cb2d 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { TranslateService } from '@ngx-translate/core'; @@ -12,6 +12,7 @@ import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core import { Registration } from '../../../core/shared/registration.model'; import { hasNoValue, hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; @Component({ selector: 'ds-confirm-email', @@ -41,6 +42,7 @@ export class ConfirmEmailComponent implements OnDestroy { externalLocation: string; constructor( + @Inject(NativeWindowService) protected _window: NativeWindowRef, private formBuilder: FormBuilder, private externalLoginService: ExternalLoginService, private epersonDataService: EPersonDataService, @@ -137,7 +139,11 @@ export class ConfirmEmailComponent implements OnDestroy { } else if (rd.hasSucceeded) { // set Redirect URL to User profile, so the user is redirected to the profile page after logging in this.authService.setRedirectUrl('/profile'); - const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, location); + const externalServerUrl = this.authService.getExternalServerRedirectUrl( + this._window.nativeWindow.origin, + redirectRoute, + location + ); // redirect to external registration type authentication url this.hardRedirectService.redirect(externalServerUrl); } diff --git a/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts index 9b4bd2b8242..63559ef904c 100644 --- a/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts +++ b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.spec.ts @@ -22,6 +22,8 @@ import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { ExternalLoginService } from '../../external-log-in/services/external-login.service'; +import { NativeWindowService } from '../../core/services/window.service'; +import { NativeWindowMockFactory } from '../../shared/mocks/mock-native-window-ref'; describe('ReviewAccountInfoComponent', () => { let component: ReviewAccountInfoComponent; @@ -82,6 +84,7 @@ describe('ReviewAccountInfoComponent', () => { await TestBed.configureTestingModule({ declarations: [ReviewAccountInfoComponent, CompareValuesPipe], providers: [ + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, { provide: EPersonDataService, useValue: ePersonDataServiceStub }, { provide: NgbModal, useValue: modalStub }, { diff --git a/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts index aee858c339c..f388cffb91d 100644 --- a/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts +++ b/src/app/external-login-review-account-info-page/review-account-info/review-account-info.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, OnInit, } from '@angular/core'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { combineLatest, filter, from, map, Observable, Subscription, switchMap, take, tap } from 'rxjs'; @@ -14,6 +14,7 @@ import { AuthService } from '../../core/auth/auth.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { AuthRegistrationType } from '../../core/auth/models/auth.registration-type'; import { ExternalLoginService } from '../../external-log-in/services/external-login.service'; +import { NativeWindowRef, NativeWindowService } from '../../core/services/window.service'; export interface ReviewAccountInfoData { label: string; @@ -53,6 +54,7 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { subs: Subscription[] = []; constructor( + @Inject(NativeWindowService) protected _window: NativeWindowRef, private ePersonService: EPersonDataService, private modalService: NgbModal, private notificationService: NotificationsService, @@ -211,7 +213,11 @@ export class ReviewAccountInfoComponent implements OnInit, OnDestroy { ); // set Redirect URL to User profile, so the user is redirected to the profile page after logging in this.authService.setRedirectUrl('/profile'); - const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, location); + const externalServerUrl = this.authService.getExternalServerRedirectUrl( + this._window.nativeWindow.origin, + redirectRoute, + location + ); // redirect to external registration type authentication url this.hardRedirectService.redirect(externalServerUrl); } else if (response.hasFailed) { diff --git a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts index 32b551e05cd..e52cfcc40c6 100644 --- a/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts @@ -91,7 +91,11 @@ export class LogInExternalProviderComponent implements OnInit { } else if (isEmpty(redirectRoute)) { redirectRoute = '/'; } - const externalServerUrl = this.authService.getExternalServerRedirectUrl(redirectRoute, this.location); + const externalServerUrl = this.authService.getExternalServerRedirectUrl( + this._window.nativeWindow.origin, + redirectRoute, + this.location + ); // redirect to shibboleth/orcid/(external) authentication url this.hardRedirectService.redirect(externalServerUrl); }); From 327d8b5a66790dcc8154f0e278454f999e321322 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 11 Oct 2023 16:28:18 +0200 Subject: [PATCH 53/56] [DSC-337] Fix for merge and refactoring --- src/app/core/layout/tab-data.service.spec.ts | 507 +++++++++++++++++- src/app/core/layout/tab-data.service.ts | 61 ++- .../item-page/cris-item-page-tab.resolver.ts | 1 + 3 files changed, 537 insertions(+), 32 deletions(-) diff --git a/src/app/core/layout/tab-data.service.spec.ts b/src/app/core/layout/tab-data.service.spec.ts index 256bff1618f..b4620f18102 100644 --- a/src/app/core/layout/tab-data.service.spec.ts +++ b/src/app/core/layout/tab-data.service.spec.ts @@ -16,7 +16,8 @@ import { of } from 'rxjs'; import { FindListOptions } from '../data/find-list-options.model'; import { RequestParam } from '../cache/models/request-param.model'; import { createPaginatedList } from '../../shared/testing/utils.test'; -import { bothTabs } from '../../shared/testing/layout-tab.mocks'; +import objectContaining = jasmine.objectContaining; +import arrayContaining = jasmine.arrayContaining; describe('TabDataService', () => { let scheduler: TestScheduler; @@ -75,6 +76,484 @@ describe('TabDataService', () => { } }; + const tabWithOnlyMinors: CrisLayoutTab = { + type: TAB, + id: 4, + shortname: 'person-bibliometrics', + header: 'person-bibliometrics-header', + entityType: 'Person', + priority: 0, + security: 0, + rows: [ + { + style: '', + cells: [ + { + style: '', + boxes: [ + { + id: 3418, + shortname: 'heading', + header: null, + entityType: 'Person', + collapsed: false, + minor: true, + style: null, + security: 0, + boxType: 'METADATA', + maxColumn: null, + clear: false, + configuration: { + id: '1', + type: 'boxmetadataconfiguration', + rows: [ + { + style: '', + cells: [ + { + style: '', + fields: [ + { + metadata: 'dc.title', + label: null, + rendering: 'heading', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + } + ] + } + ] + }, + metadataSecurityFields: [], + container: false + } + ] + } + ] + }, + { + style: '', + cells: [ + { + style: '', + boxes: [ + { + id: 3419, + shortname: 'namecard', + header: 'Name Card', + entityType: 'Person', + collapsed: false, + minor: true, + style: null, + security: 0, + boxType: 'METADATA', + maxColumn: null, + clear: false, + configuration: { + id: '0', + type: 'boxmetadataconfiguration', + rows: [ + { + style: '', + cells: [ + { + style: 'col-3', + fields: [ + { + bitstream: { + bundle: 'ORIGINAL', + metadataField: 'dc.type', + metadataValue: 'personal pictur' + }, + label: null, + rendering: 'thumbnail', + fieldType: 'BITSTREAM', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + }, + { + style: 'px-2', + fields: [ + { + metadata: 'dc.title', + label: 'Preferred name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name', + label: 'Official Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name.translated', + label: 'Translated Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name.variant', + label: 'Alternative Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.affiliation.name', + label: 'Main Affiliation', + rendering: 'crisref', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.workgroup', + label: null, + rendering: 'crisref', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'oairecerif.identifier.url', + label: 'Web Site', + rendering: 'link', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.email', + label: 'Email', + rendering: 'crisref.email', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.orcid', + label: 'ORCID', + rendering: 'orcid', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.scopus-author-id', + label: 'Scopus Author ID', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.rid', + label: 'Researcher ID', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + } + ] + } + ] + }, + metadataSecurityFields: [], + container: false + } + ] + } + ] + } + ], + uuid: 'person-bibliometrics-4', + _links: { + self: { + href: 'https://rest.api/rest/api/tabs/3' + } + } + }; + + const tabWithSomeMinors: CrisLayoutTab = { + type: TAB, + id: 5, + shortname: 'person-bibliometrics', + header: 'person-bibliometrics-header', + entityType: 'Person', + priority: 0, + security: 0, + rows: [ + { + style: '', + cells: [ + { + style: '', + boxes: [ + { + id: 3418, + shortname: 'heading', + header: null, + entityType: 'Person', + collapsed: false, + minor: false, + style: null, + security: 0, + boxType: 'METADATA', + maxColumn: null, + clear: false, + configuration: { + id: '3', + type: 'boxmetadataconfiguration', + rows: [ + { + style: '', + cells: [ + { + style: '', + fields: [ + { + metadata: 'dc.title', + label: null, + rendering: 'heading', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + } + ] + } + ] + }, + metadataSecurityFields: [], + container: false + } + ] + } + ] + }, + { + style: '', + cells: [ + { + style: '', + boxes: [ + { + id: 3419, + shortname: 'namecard', + header: 'Name Card', + entityType: 'Person', + collapsed: false, + minor: true, + style: null, + security: 0, + boxType: 'METADATA', + maxColumn: null, + clear: false, + configuration: { + id: '0', + type: 'boxmetadataconfiguration', + rows: [ + { + style: '', + cells: [ + { + style: 'col-3', + fields: [ + { + bitstream: { + bundle: 'ORIGINAL', + metadataField: 'dc.type', + metadataValue: 'personal pictur' + }, + label: null, + rendering: 'thumbnail', + fieldType: 'BITSTREAM', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + }, + { + style: 'px-2', + fields: [ + { + metadata: 'dc.title', + label: 'Preferred name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name', + label: 'Official Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name.translated', + label: 'Translated Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.name.variant', + label: 'Alternative Name', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.affiliation.name', + label: 'Main Affiliation', + rendering: 'crisref', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'crisrp.workgroup', + label: null, + rendering: 'crisref', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'oairecerif.identifier.url', + label: 'Web Site', + rendering: 'link', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.email', + label: 'Email', + rendering: 'crisref.email', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.orcid', + label: 'ORCID', + rendering: 'orcid', + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.scopus-author-id', + label: 'Scopus Author ID', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + }, + { + metadata: 'person.identifier.rid', + label: 'Researcher ID', + rendering: null, + fieldType: 'METADATA', + styleLabel: 'font-weight-bold col-3', + styleValue: null, + labelAsHeading: false, + valuesInline: false + } + ] + } + ] + } + ] + }, + metadataSecurityFields: [], + container: false + } + ] + } + ] + } + ], + uuid: 'person-bibliometrics-5', + _links: { + self: { + href: 'https://rest.api/rest/api/tabs/3' + } + } + }; + const endpointURL = `https://rest.api/rest/api/tabs`; const requestURL = `https://rest.api/rest/api/tabs/${tabPersonProfile.id}`; const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a'; @@ -82,10 +561,13 @@ describe('TabDataService', () => { const entityType = 'Person'; const tabId = '1'; - const array = [tabPersonProfile, tabPersonBiography, tabPersonBibliometrics]; + const array = [tabPersonProfile, tabPersonBiography, tabPersonBibliometrics, tabWithOnlyMinors, tabWithSomeMinors]; const paginatedList = createPaginatedList(array); const tabRD = createSuccessfulRemoteDataObject(tabPersonProfile); const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + const noMinorsList = + createPaginatedList([tabPersonProfile, tabPersonBiography, tabPersonBibliometrics, tabWithSomeMinors]); + const paginatedListWithoutMinorsRD = createSuccessfulRemoteDataObject(noMinorsList); beforeEach(() => { scheduler = getTestScheduler(); @@ -168,6 +650,19 @@ describe('TabDataService', () => { expect(result).toBeObservable(expected); }); + it('should remove tab with minor cells', () => { + const result = service.findByItem(itemUUID, true, true); + result.subscribe(tabs => { + expect(tabs.payload.page).toHaveSize(4); + expect(tabs.payload.page).not.toEqual( + arrayContaining([objectContaining({ id: tabWithOnlyMinors.id })]) + ); + expect(tabs.payload.page).toEqual( + arrayContaining([objectContaining({ id: tabWithSomeMinors.id })]) + ); + }); + }); + }); describe('searchByEntityType', () => { @@ -191,12 +686,4 @@ 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 2a9c1663539..2e9437742fe 100644 --- a/src/app/core/layout/tab-data.service.ts +++ b/src/app/core/layout/tab-data.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { CrisLayoutTab } from './models/tab.model'; +import { CrisLayoutCell, CrisLayoutRow, CrisLayoutTab } from './models/tab.model'; import { RequestService } from '../data/request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -12,11 +12,13 @@ import { TAB } from './models/tab.resource-type'; import { dataService } from '../data/base/data-service.decorator'; import { RemoteData } from '../data/remote-data'; import { PaginatedList } from '../data/paginated-list.model'; -import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { FindListOptions } from '../data/find-list-options.model'; import { RequestParam } from '../cache/models/request-param.model'; import { IdentifiableDataService } from '../data/base/identifiable-data.service'; import { SearchDataImpl } from '../data/base/search-data'; +import { map } from 'rxjs/operators'; +import { hasNoValue, hasValue } from '../../shared/empty.util'; +import { CrisLayoutBox } from './models/box.model'; /** * A service responsible for fetching data from the REST API on the tabs endpoint @@ -54,43 +56,58 @@ export class TabDataService extends IdentifiableDataService { * available data. Empty tabs are filter out. * @param itemUuid UUID of the Item * @param useCachedVersionIfAvailable - * @param linkToFollow */ - findByItem(itemUuid: string, useCachedVersionIfAvailable, excludeMinors?: boolean ,linkToFollow?: FollowLinkConfig): Observable>> { + findByItem( + itemUuid: string, useCachedVersionIfAvailable: boolean, excludeMinors?: boolean + ): Observable>> { const options = new FindListOptions(); options.searchParams = [new RequestParam('uuid', itemUuid)]; - return this.searchData.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; - })); + return this.searchData.searchBy(this.searchFindByItem, options, useCachedVersionIfAvailable) + .pipe( + map((data) => { + if (hasValue(data?.payload?.page) && excludeMinors) { + data.payload.page = this.filterTabWithOnlyMinor(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)); + filterTabWithOnlyMinor(tabs: CrisLayoutTab[]): CrisLayoutTab[] { + return tabs.filter(tab => !this.hasTabOnlyMinor(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 false; - } - } - } + hasTabOnlyMinor(tab: CrisLayoutTab): boolean { + if (hasNoValue(tab?.rows)) { + return false; } - return true; + return tab.rows.every(row => this.hasRowOnlyMinor(row)); + } + + hasRowOnlyMinor(row: CrisLayoutRow): boolean { + if (hasNoValue(row?.cells)) { + return false; + } + return row.cells.every(cell => this.hasCellOnlyMinor(cell)); + } + + hasCellOnlyMinor(cell: CrisLayoutCell): boolean { + if (hasNoValue(cell?.boxes)) { + return false; + } + return cell.boxes.every(box => this.isMinor(box)); + } + + isMinor(box: CrisLayoutBox): boolean { + return box.minor === true; } /** 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 5efc7d311f3..e601c75fbdc 100644 --- a/src/app/item-page/cris-item-page-tab.resolver.ts +++ b/src/app/item-page/cris-item-page-tab.resolver.ts @@ -37,6 +37,7 @@ export class CrisItemPageTabResolver implements Resolve Date: Wed, 11 Oct 2023 17:37:38 +0200 Subject: [PATCH 54/56] [DSC-1251] Refactoring --- .../file-download-link/file-download-link.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index d2f1d215c0d..025cb2ce537 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -4,8 +4,8 @@ import { getBitstreamDownloadRoute, getBitstreamRequestACopyRoute } from '../../ import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { hasValue, isNotEmpty } from '../empty.util'; -import { map } from 'rxjs/operators'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, shareReplay } from 'rxjs'; import { Item } from '../../core/shared/item.model'; import { ConfigurationDataService } from '../../core/data/configuration-data.service'; import { getFirstCompletedRemoteData, getRemoteDataPayload } from 'src/app/core/shared/operators'; @@ -79,6 +79,8 @@ export class FileDownloadLinkComponent implements OnInit { // in case requestItemType empty/commented out(undefined) - request-copy not allowed hasValue(requestItemType) && requestItemType.values.length > 0 ), + catchError(() => observableOf(false)), + shareReplay(1) ); } else { this.bitstreamPath$ = observableOf(this.getBitstreamDownloadPath()); From f3072b89916df988c6eeffa99826bbbf06ec29bd Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 11 Oct 2023 19:11:50 +0200 Subject: [PATCH 55/56] [CST-12212] Prefill email with the one received from registration data --- .../confirm-email/confirm-email.component.html | 1 + .../confirm-email/confirm-email.component.spec.ts | 11 ++++++++++- .../confirm-email/confirm-email.component.ts | 10 ++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html index 455aaf75e73..69b65341549 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html @@ -10,6 +10,7 @@

formControlName="email" placeholder="profile.email@example.com" class="form-control form-control-lg position-relative" + data-test="emailInput" />
{ it('should call postCreateAccountFromToken if email is confirmed', () => { component.emailForm.setValue({ email: 'test@example.com' }); diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts index eaecf71cb2d..e996babddce 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ExternalLoginService } from '../../services/external-login.service'; import { TranslateService } from '@ngx-translate/core'; @@ -20,7 +20,7 @@ import { NativeWindowRef, NativeWindowService } from '../../../core/services/win styleUrls: ['./confirm-email.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ConfirmEmailComponent implements OnDestroy { +export class ConfirmEmailComponent implements OnInit, OnDestroy { /** * The form containing the email input */ @@ -51,12 +51,14 @@ export class ConfirmEmailComponent implements OnDestroy { private authService: AuthService, private hardRedirectService: HardRedirectService, ) { + } + + ngOnInit() { this.emailForm = this.formBuilder.group({ - email: ['', [Validators.required, Validators.email]] + email: [this.registrationData.email, [Validators.required, Validators.email]] }); } - /** * Submits the email form and performs appropriate actions based on the form's validity and user input. * If the form is valid and the confirmed email matches the registration email, calls the postCreateAccountFromToken method with the token and registration data. From c012d4a5195638d5c822387ce7cc779c2b698c1f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 11 Oct 2023 19:17:32 +0200 Subject: [PATCH 56/56] [CST-12212] Add input placeholder label --- .../confirm-email/confirm-email.component.html | 2 +- .../provide-email/provide-email.component.html | 2 +- .../external-log-in/external-log-in.component.html | 2 +- src/assets/i18n/en.json5 | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html index 69b65341549..43848a2d3e5 100644 --- a/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html +++ b/src/app/external-log-in/email-confirmation/confirm-email/confirm-email.component.html @@ -8,7 +8,7 @@

type="email" id="email" formControlName="email" - placeholder="profile.email@example.com" + placeholder="{{'external-login.form.email' | translate}}" class="form-control form-control-lg position-relative" data-test="emailInput" /> diff --git a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html index 46e804e1c2e..ab95d3986eb 100644 --- a/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html +++ b/src/app/external-log-in/email-confirmation/provide-email/provide-email.component.html @@ -8,7 +8,7 @@

type="email" id="email" formControlName="email" - placeholder="Input box" + placeholder="{{'external-login.form.email' | translate}}" class="form-control form-control-lg position-relative" /> diff --git a/src/app/external-log-in/external-log-in/external-log-in.component.html b/src/app/external-log-in/external-log-in/external-log-in.component.html index aefa9719669..5fdb56f626c 100644 --- a/src/app/external-log-in/external-log-in/external-log-in.component.html +++ b/src/app/external-log-in/external-log-in/external-log-in.component.html @@ -18,7 +18,7 @@

{{ 'external-login.confirmation.header' | translate}}

-

or

+

{{'external-login.form.or-divider' | translate}}