Skip to content

Commit

Permalink
[#13102] Admin rejecting account request: give actual account in the …
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
3 people committed Jun 28, 2024
1 parent 40350ef commit efeffd4
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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]}
>
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { of } from 'rxjs';
import { RejectWithReasonModalComponent } from './admin-reject-with-reason-modal.component';
import {
FeedbackSessionsGroup, InstructorAccountSearchResult,
SearchService,
} from '../../../../services/search.service';
import { StatusMessageService } from '../../../../services/status-message.service';
import { createBuilder } from '../../../../test-helpers/generic-builder';

const DEFAULT_FEEDBACK_SESSION_GROUP: FeedbackSessionsGroup = {
sessionName: {
feedbackSessionUrl: 'sessionUrl',
startTime: 'startTime',
endTime: 'endTime',
},
};

const instructorAccountSearchResultBuilder = createBuilder<InstructorAccountSearchResult>({
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<RejectWithReasonModalComponent>;
let component: RejectWithReasonModalComponent;
Expand All @@ -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;
Expand All @@ -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();

Expand All @@ -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();

Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 = '<p>Hi, {accountRequestName} </p>\n\n'
+ '<p>Thanks for your interest in using TEAMMATES. '
+ 'We are unable to create a TEAMMATES instructor account for you.</p>'
Expand All @@ -33,24 +53,62 @@ export class RejectWithReasonModalComponent implements OnInit {
+ '<strong>Remedy:</strong> If you are a student but you still need an instructor account, '
+ 'please send your justification to {supportEmail}</p>\n\n'
+ '<p><strong>Reason:</strong> You already have an account for this email address and this institution.<br />'
+ '<strong>Remedy:</strong> You can login to TEAMMATES using your Google account {existingEmail} </p>\n\n'
+ '<strong>Remedy:</strong> You can login to TEAMMATES using your Google account: {googleId} </p>\n\n'
+ '<p>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.</p>'
+ '<p>If you need further clarification or would like to appeal this decision, please '
+ 'feel free to contact us at {supportEmail}</p>'
+ '<p>Regards,<br />TEAMMATES Team.</p>';
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.
*/
Expand Down

0 comments on commit efeffd4

Please sign in to comment.