From 9f9df8f7266e68f3047233aec602340882b03ed8 Mon Sep 17 00:00:00 2001
From: James Hollinger <39168456+james-hollinger@users.noreply.github.com>
Date: Mon, 19 Aug 2024 17:13:13 -0700
Subject: [PATCH] PIDP-936 - Email lookup and pre-approval for endorsements
(#586)
* update existing tests
* update approval + tests
* formatting
* continue wip
* finish wip
* handle enter keydown
* prevent navigation and/or multiple dialog boxes
* formatting
* update tests
* fix
---------
Co-authored-by: Panos Hatzinikolaou
---
.../Features/EndorsementRequests/Approve.cs | 122 +-
.../Features/EndorsementRequests/Create.cs | 121 ++
...1205921_PreApprovedEndorsement.Designer.cs | 1482 +++++++++++++++++
.../20240731205921_PreApprovedEndorsement.cs | 29 +
.../Migrations/PidpDbContextModelSnapshot.cs | 3 +
.../Features/EndorsementRequests/Approve.cs | 60 +-
.../Features/EndorsementRequests/Create.cs | 55 +-
.../EndorsementRequests/EmailSearch.cs | 47 +
.../EndorsementRequestsController.cs | 11 +-
backend/webapi/Models/EndorsementRequest.cs | 6 +-
.../endorsements-resource.service.ts | 26 +-
.../pages/endorsements/endorsements.page.html | 5 +-
.../pages/endorsements/endorsements.page.ts | 77 +-
.../models/endorsement-email-search.model.ts | 3 +
.../endorsement-request-information.model.ts | 4 +-
.../models/endorsement-request.model.ts | 1 +
16 files changed, 1932 insertions(+), 120 deletions(-)
create mode 100644 backend/webapi.tests/Features/EndorsementRequests/Create.cs
create mode 100644 backend/webapi/Data/Migrations/20240731205921_PreApprovedEndorsement.Designer.cs
create mode 100644 backend/webapi/Data/Migrations/20240731205921_PreApprovedEndorsement.cs
create mode 100644 backend/webapi/Features/EndorsementRequests/EmailSearch.cs
create mode 100644 workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-email-search.model.ts
diff --git a/backend/webapi.tests/Features/EndorsementRequests/Approve.cs b/backend/webapi.tests/Features/EndorsementRequests/Approve.cs
index b2c9782ae..8b4a5293a 100644
--- a/backend/webapi.tests/Features/EndorsementRequests/Approve.cs
+++ b/backend/webapi.tests/Features/EndorsementRequests/Approve.cs
@@ -12,12 +12,12 @@ namespace PidpTests.Features.EndorsementRequests;
using PidpTests.TestingExtensions;
-public class EndorsementApproveTests : InMemoryDbTest
+public class EndorsementRequestApproveTests : InMemoryDbTest
{
- private static readonly int RequestingPartyId = 1;
- private static readonly int ReceivingPartyId = 2;
+ private const int RequestingPartyId = 1;
+ private const int ReceivingPartyId = 2;
- public EndorsementApproveTests()
+ public EndorsementRequestApproveTests()
{
this.TestDb.HasAParty(party =>
{
@@ -41,7 +41,7 @@ public EndorsementApproveTests()
[Theory]
[MemberData(nameof(StatusCases))]
- public async void Approve_AsRequestingParty_SuccessOnApproved(EndorsementRequestStatus status)
+ public async Task Approve_AsRequestingParty_SuccessOnApproved(EndorsementRequestStatus status)
{
var request = this.TestDb.Has(new EndorsementRequest
{
@@ -55,21 +55,20 @@ public async void Approve_AsRequestingParty_SuccessOnApproved(EndorsementRequest
var result = await handler.HandleAsync(new Approve.Command { EndorsementRequestId = request.Id, PartyId = RequestingPartyId });
+ Assert.Equal(expected, result.IsSuccess);
if (expected)
{
- Assert.True(result.IsSuccess);
Assert.Equal(EndorsementRequestStatus.Completed, request.Status);
}
else
{
- Assert.False(result.IsSuccess);
Assert.Equal(status, request.Status);
}
}
[Theory]
[MemberData(nameof(StatusCases))]
- public async void Approve_AsRecievingParty_SuccessOnRecieved(EndorsementRequestStatus status)
+ public async Task Approve_AsRecievingPartyNotPreApproved_SuccessOnRecieved(EndorsementRequestStatus status)
{
var request = this.TestDb.Has(new EndorsementRequest
{
@@ -82,29 +81,66 @@ public async void Approve_AsRecievingParty_SuccessOnRecieved(EndorsementRequestS
var result = await handler.HandleAsync(new Approve.Command { EndorsementRequestId = request.Id, PartyId = ReceivingPartyId });
+ Assert.Equal(expected, result.IsSuccess);
if (expected)
{
- Assert.True(result.IsSuccess);
Assert.Equal(EndorsementRequestStatus.Approved, request.Status);
}
else
{
- Assert.False(result.IsSuccess);
Assert.Equal(status, request.Status);
}
}
- public static IEnumerable
Email
-
+
Required
diff --git a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/endorsements.page.ts b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/endorsements.page.ts
index 3add699b4..3568cde0b 100644
--- a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/endorsements.page.ts
+++ b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/endorsements.page.ts
@@ -58,6 +58,7 @@ import { LoggerService } from '@app/core/services/logger.service';
import { UtilsService } from '@app/core/services/utils.service';
import { StatusCode } from '@app/features/portal/enums/status-code.enum';
import { LookupService } from '@app/modules/lookup/lookup.service';
+import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';
import { EndorsementCardComponent } from './components/endorsement-card/endorsement-card.component';
import {
@@ -67,9 +68,9 @@ import {
import { EndorsementsFormState } from './endorsements-form-state';
import { EndorsementsResource } from './endorsements-resource.service';
import { EndorsementRequestStatus } from './enums/endorsement-request-status.enum';
+import { EndorsementEmailSearch } from './models/endorsement-email-search.model';
import { EndorsementRequest } from './models/endorsement-request.model';
import { Endorsement } from './models/endorsement.model';
-import { BreadcrumbComponent } from '@app/shared/components/breadcrumb/breadcrumb.component';
export enum EndorsementType {
WorkingRelationship,
@@ -116,6 +117,7 @@ export class EndorsementsPage
public endorsements$!: Observable;
public showOverlayOnSubmit = true;
+ public isDialogOpen = false;
public constructor(
dependenciesService: AbstractFormDependenciesService,
@@ -166,6 +168,18 @@ export class EndorsementsPage
class: 'dialog-container',
};
+ public onEnter(event: Event): void {
+ event.preventDefault();
+ if (!this.isDialogOpen) {
+ const sendButton = document.querySelector(
+ 'button[type="submit"]',
+ ) as HTMLButtonElement;
+ if (sendButton) {
+ sendButton.click();
+ }
+ }
+ }
+
public get recipientEmail(): FormControl {
return this.formState.form.get('recipientEmail') as FormControl;
}
@@ -193,10 +207,13 @@ export class EndorsementsPage
'You are about to approve this endorsement, would you like to proceed',
};
+ this.isDialogOpen = true;
+
this.dialog
.open(ConfirmDialogComponent, { data })
.afterClosed()
.pipe(
+ tap(() => (this.isDialogOpen = false)),
exhaustMap((result) => {
this.loadingOverlayService.close();
return result
@@ -223,10 +240,12 @@ export class EndorsementsPage
content:
'You are about to cancel this endorsement, would you like to proceed',
};
+ this.isDialogOpen = true;
this.dialog
.open(ConfirmDialogComponent, { data })
.afterClosed()
.pipe(
+ tap(() => (this.isDialogOpen = false)),
exhaustMap((result) => {
this.loadingOverlayService.close();
return result
@@ -253,10 +272,12 @@ export class EndorsementsPage
content:
'You are about to cancel this endorsement, would you like to proceed',
};
+ this.isDialogOpen = true;
this.dialog
.open(ConfirmDialogComponent, { data })
.afterClosed()
.pipe(
+ tap(() => (this.isDialogOpen = false)),
exhaustMap((result) =>
result
? this.resource
@@ -321,6 +342,7 @@ export class EndorsementsPage
protected performSubmission(): NoContent {
const partyId = this.partyService.partyId;
+
const data: DialogOptions = {
title: 'Endorsement requests',
bottomBorder: false,
@@ -328,10 +350,7 @@ export class EndorsementsPage
bodyTextPosition: 'center',
component: HtmlComponent,
data: {
- content:
- "You are about to request an endorsement to" +
- this.formState.json?.recipientEmail +
- '
would you like to proceed?',
+ content: '',
},
imageSrc: '/assets/images/online-marketing-hIgeoQjS_iE-unsplash.jpg',
imageType: 'banner',
@@ -342,26 +361,38 @@ export class EndorsementsPage
class: 'dialog-container',
};
+ this.isDialogOpen = true;
return partyId && this.formState.json
- ? this.dialog
- .open(ConfirmDialogComponent, { data })
- .afterClosed()
+ ? this.resource
+ .emailSearch(partyId, this.formState.json.recipientEmail)
.pipe(
- exhaustMap((result) => {
- this.loadingOverlayService.close();
- return result && partyId && this.formState.json
- ? this.resource.createEndorsementRequest(
- partyId,
- this.formState.json,
- )
- : EMPTY;
- }),
- catchError((error: HttpErrorResponse) => {
- this.loadingOverlayService.close();
- if (error.status === HttpStatusCode.BadRequest) {
- return of(noop());
- }
- return of(noop());
+ switchMap((response: EndorsementEmailSearch) => {
+ data.data!.content = response.recipientName
+ ? `An existing user has registered ${this.formState.json?.recipientEmail}. Please confirm you are wanting an endorsement with ${response.recipientName}
`
+ : `You are about to request an endorsement to${this.formState.json?.recipientEmail}
would you like to proceed?`;
+
+ return this.dialog
+ .open(ConfirmDialogComponent, { data })
+ .afterClosed()
+ .pipe(
+ tap(() => (this.isDialogOpen = false)),
+ exhaustMap((result) => {
+ this.loadingOverlayService.close();
+ return result && partyId && this.formState.json
+ ? this.resource.createEndorsementRequest(partyId, {
+ ...this.formState.json,
+ preApproved: response.recipientName ? true : false,
+ })
+ : EMPTY;
+ }),
+ catchError((error: HttpErrorResponse) => {
+ this.loadingOverlayService.close();
+ if (error.status === HttpStatusCode.BadRequest) {
+ return of(noop());
+ }
+ return of(noop());
+ }),
+ );
}),
)
: EMPTY;
diff --git a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-email-search.model.ts b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-email-search.model.ts
new file mode 100644
index 000000000..90578573a
--- /dev/null
+++ b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-email-search.model.ts
@@ -0,0 +1,3 @@
+export interface EndorsementEmailSearch {
+ recipientName: string | null;
+}
diff --git a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request-information.model.ts b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request-information.model.ts
index 5a68bfbd6..1e565281c 100644
--- a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request-information.model.ts
+++ b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request-information.model.ts
@@ -4,5 +4,5 @@ import { EndorsementRequest } from './endorsement-request.model';
export interface EndorsementRequestInformation
extends Pick<
EndorsementRequest,
- 'recipientEmail' | 'additionalInformation'
- > {}
+ 'recipientEmail' | 'additionalInformation' | 'preApproved'
+ > { }
diff --git a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request.model.ts b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request.model.ts
index 6681682f9..039713885 100644
--- a/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request.model.ts
+++ b/workspace/apps/pidp/src/app/features/organization-info/pages/endorsements/models/endorsement-request.model.ts
@@ -9,4 +9,5 @@ export interface EndorsementRequest {
status: EndorsementRequestStatus;
statusDate: string;
actionable: boolean;
+ preApproved: boolean;
}