Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#13102] Admin rejecting account request: give actual account in the email template #13118

Merged
Merged
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
Loading