From efeffd4d84e2045ceb1a72c8c255d30b7a6d0677 Mon Sep 17 00:00:00 2001
From: Andy <128531452+Andy-W-Developer@users.noreply.github.com>
Date: Sat, 29 Jun 2024 01:04:55 +1200
Subject: [PATCH] [#13102] Admin rejecting account request: give actual account
in the email template (#13118)
* Change existingEmail to googleId
* Add extra info to instructions
* Fix missing googleId if multiple accounts exist
* Add function name to test name
* Move account search to function
* Change missing googleId string to be shorter
* Add check for accounts with no googleId
* Add tests for replaceGoogleId
* Change instructor search result to use createBuilder
* Change a test name to fit convention
---------
Co-authored-by: domoberzin <74132255+domoberzin@users.noreply.github.com>
Co-authored-by: Wei Qing <48304907+weiquu@users.noreply.github.com>
---
...t-with-reason-modal.component.spec.ts.snap | 2 +
...reject-with-reason-modal.component.spec.ts | 71 +++++++++++++++++--
...dmin-reject-with-reason-modal.component.ts | 64 ++++++++++++++++-
3 files changed, 130 insertions(+), 7 deletions(-)
diff --git a/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/__snapshots__/admin-reject-with-reason-modal.component.spec.ts.snap b/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/__snapshots__/admin-reject-with-reason-modal.component.spec.ts.snap
index 6d39aa0d076..f657de589c6 100644
--- a/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/__snapshots__/admin-reject-with-reason-modal.component.spec.ts.snap
+++ b/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/__snapshots__/admin-reject-with-reason-modal.component.spec.ts.snap
@@ -5,8 +5,10 @@ exports[`RejectWithReasonModal should show empty title and body 1`] = `
accountRequestEmail=""
accountRequestName=""
activeModal={[Function NgbActiveModal]}
+ existingAccount={[Function Object]}
rejectionReasonBody={[Function String]}
rejectionReasonTitle={[Function String]}
+ searchService={[Function SearchService]}
statusMessageService={[Function StatusMessageService]}
>
({
+ name: 'name',
+ email: 'email',
+ googleId: 'googleId',
+ courseId: 'courseId',
+ courseName: 'courseName',
+ isCourseDeleted: false,
+ institute: 'institute',
+ courseJoinLink: 'courseJoinLink',
+ homePageLink: 'homePageLink',
+ manageAccountLink: 'manageAccountLink',
+ showLinks: false,
+ awaitingSessions: DEFAULT_FEEDBACK_SESSION_GROUP,
+ openSessions: DEFAULT_FEEDBACK_SESSION_GROUP,
+ notOpenSessions: DEFAULT_FEEDBACK_SESSION_GROUP,
+ publishedSessions: DEFAULT_FEEDBACK_SESSION_GROUP,
+});
describe('RejectWithReasonModal', () => {
+ let searchService: SearchService;
let statusMessageService: StatusMessageService;
let fixture: ComponentFixture
;
let component: RejectWithReasonModalComponent;
@@ -15,14 +49,16 @@ describe('RejectWithReasonModal', () => {
declarations: [],
imports: [
HttpClientTestingModule,
+ RouterTestingModule,
],
- providers: [NgbActiveModal, StatusMessageService],
+ providers: [NgbActiveModal, SearchService, StatusMessageService],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RejectWithReasonModalComponent);
+ searchService = TestBed.inject(SearchService);
statusMessageService = TestBed.inject(StatusMessageService);
fixture.detectChanges();
component = fixture.componentInstance;
@@ -36,7 +72,34 @@ describe('RejectWithReasonModal', () => {
expect(fixture).toMatchSnapshot();
});
- it('should show error message when title is empty upon submitting', () => {
+ it('replaceGoogleId: should set the googleId to an empty string if no instructor accounts are found', () => {
+ jest.spyOn(searchService, 'searchAdmin').mockReturnValue(of({
+ students: [],
+ instructors: [],
+ accountRequests: [],
+ }));
+
+ component.replaceGoogleId();
+
+ expect(component.existingAccount.googleId).toEqual('');
+ });
+
+ it('replaceGoogleId: should set the googleId to the instructor accounts googleId '
+ + 'if an instructor account is found', () => {
+ const testInstructor = instructorAccountSearchResultBuilder.googleId('instructorGoogleId').build();
+
+ jest.spyOn(searchService, 'searchAdmin').mockReturnValue(of({
+ students: [],
+ instructors: [testInstructor],
+ accountRequests: [],
+ }));
+
+ component.replaceGoogleId();
+
+ expect(component.existingAccount.googleId).toEqual('instructorGoogleId');
+ });
+
+ it('reject: should show error message when title is empty upon submitting', () => {
component.rejectionReasonTitle = '';
fixture.detectChanges();
@@ -51,7 +114,7 @@ describe('RejectWithReasonModal', () => {
expect(spyStatusMessageService).toHaveBeenCalled();
});
- it('should show error message when body is empty upon submitting', () => {
+ it('reject: should show error message when body is empty upon submitting', () => {
component.rejectionReasonBody = '';
fixture.detectChanges();
@@ -65,7 +128,7 @@ describe('RejectWithReasonModal', () => {
expect(spyStatusMessageService).toHaveBeenCalled();
});
- it('should close modal with data', () => {
+ it('reject: should close modal with data', () => {
const spyActiveModal = jest.spyOn(component.activeModal, 'close');
component.rejectionReasonTitle = 'Rejection Title';
component.rejectionReasonBody = 'Rejection Body';
diff --git a/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/admin-reject-with-reason-modal.component.ts b/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/admin-reject-with-reason-modal.component.ts
index 051d145d4d6..080b813161d 100644
--- a/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/admin-reject-with-reason-modal.component.ts
+++ b/src/web/app/components/account-requests-table/admin-reject-with-reason-modal/admin-reject-with-reason-modal.component.ts
@@ -2,7 +2,9 @@ import { Component, Input, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { RejectWithReasonModalComponentResult } from './admin-reject-with-reason-modal-model';
import { environment } from '../../../../environments/environment';
+import { AdminSearchResult, InstructorAccountSearchResult, SearchService } from '../../../../services/search.service';
import { StatusMessageService } from '../../../../services/status-message.service';
+import { ErrorMessageOutput } from '../../../error-message-output';
/**
* Modal to select reject account requests with reason.
@@ -21,6 +23,24 @@ export class RejectWithReasonModalComponent implements OnInit {
@Input()
accountRequestEmail: string = '';
+ existingAccount: InstructorAccountSearchResult = {
+ name: '',
+ email: '',
+ googleId: '',
+ courseId: '',
+ courseName: '',
+ isCourseDeleted: false,
+ institute: '',
+ courseJoinLink: '',
+ homePageLink: '',
+ manageAccountLink: '',
+ showLinks: false,
+ awaitingSessions: {},
+ openSessions: {},
+ notOpenSessions: {},
+ publishedSessions: {},
+ };
+
rejectionReasonBody: string = 'Hi, {accountRequestName}
\n\n'
+ 'Thanks for your interest in using TEAMMATES. '
+ 'We are unable to create a TEAMMATES instructor account for you.
'
@@ -33,24 +53,62 @@ export class RejectWithReasonModalComponent implements OnInit {
+ 'Remedy: If you are a student but you still need an instructor account, '
+ 'please send your justification to {supportEmail}\n\n'
+ 'Reason: You already have an account for this email address and this institution.
'
- + 'Remedy: You can login to TEAMMATES using your Google account {existingEmail}
\n\n'
+ + 'Remedy: You can login to TEAMMATES using your Google account: {googleId} \n\n'
+ + 'If you are logged into multiple Google accounts, remember to logout from other Google accounts first, '
+ + 'or use an incognito Browser window. Let us know (with a screenshot) if that doesn\'t work.
'
+ 'If you need further clarification or would like to appeal this decision, please '
+ 'feel free to contact us at {supportEmail}
'
+ 'Regards,
TEAMMATES Team.
';
rejectionReasonTitle: string = 'We are Unable to Create an Account for you';
- constructor(public activeModal: NgbActiveModal, public statusMessageService: StatusMessageService) {}
+ constructor(
+ public activeModal: NgbActiveModal,
+ public statusMessageService: StatusMessageService,
+
+ private searchService: SearchService,
+ ) {}
ngOnInit(): void {
this.rejectionReasonBody = this.rejectionReasonBody.replace('{accountRequestName}', this.accountRequestName);
- this.rejectionReasonBody = this.rejectionReasonBody.replace('{existingEmail}', this.accountRequestEmail);
this.rejectionReasonBody = this.rejectionReasonBody.replaceAll('{supportEmail}', environment.supportEmail);
+
+ this.replaceGoogleId();
}
onRejectionReasonBodyChange(updatedText: string): void {
this.rejectionReasonBody = updatedText;
}
+ replaceGoogleId(): void {
+ this.searchService.searchAdmin(this.accountRequestEmail)
+ .subscribe({
+ next: (resp: AdminSearchResult) => {
+ const hasInstructors: boolean = !!(resp.instructors && resp.instructors.length);
+
+ if (!hasInstructors) {
+ this.rejectionReasonBody = this.rejectionReasonBody.replace('{googleId}', 'NO_GOOGLEID');
+ return;
+ }
+
+ for (const instructor of resp.instructors) {
+ if (instructor.googleId !== '') {
+ this.existingAccount = instructor;
+ }
+ }
+
+ if (this.existingAccount.googleId === '') {
+ // When an instructor account exists, but for some reason does not have a googleId
+ this.rejectionReasonBody = this.rejectionReasonBody.replace('{googleId}', 'NO_GOOGLEID');
+ } else {
+ this.rejectionReasonBody = this.rejectionReasonBody.replace('{googleId}', this.existingAccount.googleId);
+ }
+ },
+ error: (resp: ErrorMessageOutput) => {
+ this.statusMessageService.showErrorToast(resp.error.message);
+ },
+ });
+ }
+
/**
* Fires the reject event.
*/