diff --git a/src/web/app/components/account-requests-table/__snapshots__/account-request-table.component.spec.ts.snap b/src/web/app/components/account-requests-table/__snapshots__/account-request-table.component.spec.ts.snap index bbf59a6a945..3b8d24519c6 100644 --- a/src/web/app/components/account-requests-table/__snapshots__/account-request-table.component.spec.ts.snap +++ b/src/web/app/components/account-requests-table/__snapshots__/account-request-table.component.spec.ts.snap @@ -4,6 +4,9 @@ exports[`AccountRequestTableComponent should display account requests with no re - + - +
- - + +
- +
diff --git a/src/web/app/components/account-requests-table/account-request-table.component.spec.ts b/src/web/app/components/account-requests-table/account-request-table.component.spec.ts index 40ccbc007c3..0541a0fa69b 100644 --- a/src/web/app/components/account-requests-table/account-request-table.component.spec.ts +++ b/src/web/app/components/account-requests-table/account-request-table.component.spec.ts @@ -211,9 +211,15 @@ describe('AccountRequestTableComponent', () => { component.searchString = 'test'; fixture.detectChanges(); - const modalSpy = jest.spyOn(simpleModalService, 'openConfirmationModal').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + const modalSpy = jest.spyOn(simpleModalService, 'openConfirmationModal').mockReturnValue(mockModalRef as any); jest.spyOn(accountService, 'resetAccountRequest').mockReturnValue(of({ joinLink: 'joinlink', @@ -245,9 +251,15 @@ describe('AccountRequestTableComponent', () => { component.searchString = 'test'; fixture.detectChanges(); - const modalSpy = jest.spyOn(simpleModalService, 'openConfirmationModal').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + const modalSpy = jest.spyOn(simpleModalService, 'openConfirmationModal').mockReturnValue(mockModalRef as any); jest.spyOn(accountService, 'resetAccountRequest').mockReturnValue(throwError(() => ({ error: { @@ -318,6 +330,9 @@ describe('AccountRequestTableComponent', () => { const mockModalRef = { componentInstance: {}, result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, }; const modalSpy = jest.spyOn(ngbModal, 'open').mockReturnValue(mockModalRef as any); diff --git a/src/web/app/components/account-requests-table/account-request-table.component.ts b/src/web/app/components/account-requests-table/account-request-table.component.ts index 3e7fc957730..7634dfc58e1 100755 --- a/src/web/app/components/account-requests-table/account-request-table.component.ts +++ b/src/web/app/components/account-requests-table/account-request-table.component.ts @@ -36,6 +36,10 @@ export class AccountRequestTableComponent { @Input() searchString = ''; + isRejectingAccount: boolean[] = new Array(this.accountRequests.length).fill(false); + isApprovingAccount: boolean[] = new Array(this.accountRequests.length).fill(false); + isResettingAccount: boolean[] = new Array(this.accountRequests.length).fill(false); + constructor( private statusMessageService: StatusMessageService, private simpleModalService: SimpleModalService, @@ -94,7 +98,8 @@ export class AccountRequestTableComponent { }, () => {}); } - approveAccountRequest(accountRequest: AccountRequestTableRowModel): void { + approveAccountRequest(accountRequest: AccountRequestTableRowModel, index: number): void { + this.isApprovingAccount[index] = true; this.accountService.approveAccountRequest(accountRequest.id, accountRequest.name, accountRequest.email, accountRequest.instituteAndCountry) .subscribe({ @@ -103,14 +108,17 @@ export class AccountRequestTableComponent { this.statusMessageService.showSuccessToast( `Account request was successfully approved. Email has been sent to ${accountRequest.email}.`, ); + this.isApprovingAccount[index] = false; }, error: (resp: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(resp.error.message); + this.isApprovingAccount[index] = false; }, }); } - resetAccountRequest(accountRequest: AccountRequestTableRowModel): void { + resetAccountRequest(accountRequest: AccountRequestTableRowModel, index: number): void { + this.isResettingAccount[index] = true; const modalContent = `Are you sure you want to reset the account request for ${accountRequest.name} with email ${accountRequest.email} from ${accountRequest.instituteAndCountry}? @@ -118,6 +126,10 @@ export class AccountRequestTableComponent { const modalRef: NgbModalRef = this.simpleModalService.openConfirmationModal( `Reset account request for ${accountRequest.name}?`, SimpleModalType.WARNING, modalContent); + modalRef.dismissed.subscribe(() => { + this.isResettingAccount[index] = false; + }); + modalRef.result.then(() => { this.accountService.resetAccountRequest(accountRequest.id) .subscribe({ @@ -125,9 +137,11 @@ export class AccountRequestTableComponent { this.statusMessageService .showSuccessToast(`Reset successful. An email has been sent to ${accountRequest.email}.`); accountRequest.registeredAtText = ''; + this.isResettingAccount[index] = false; }, error: (resp: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(resp.error.message); + this.isResettingAccount[index] = false; }, }); }, () => {}); @@ -162,24 +176,32 @@ export class AccountRequestTableComponent { modalRef.result.then(() => {}, () => {}); } - rejectAccountRequest(accountRequest: AccountRequestTableRowModel): void { + rejectAccountRequest(accountRequest: AccountRequestTableRowModel, index: number): void { + this.isRejectingAccount[index] = true; this.accountService.rejectAccountRequest(accountRequest.id) .subscribe({ next: (resp : AccountRequest) => { accountRequest.status = resp.status; this.statusMessageService.showSuccessToast('Account request was successfully rejected.'); + this.isRejectingAccount[index] = false; }, error: (resp: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(resp.error.message); + this.isRejectingAccount[index] = false; }, }); } - rejectAccountRequestWithReason(accountRequest: AccountRequestTableRowModel): void { + rejectAccountRequestWithReason(accountRequest: AccountRequestTableRowModel, index: number): void { + this.isRejectingAccount[index] = true; const modalRef: NgbModalRef = this.ngbModal.open(RejectWithReasonModalComponent); modalRef.componentInstance.accountRequestName = accountRequest.name; modalRef.componentInstance.accountRequestEmail = accountRequest.email; + modalRef.dismissed.subscribe(() => { + this.isRejectingAccount[index] = false; + }); + modalRef.result.then((res: RejectWithReasonModalComponentResult) => { this.accountService.rejectAccountRequest(accountRequest.id, res.rejectionReasonTitle, res.rejectionReasonBody) @@ -189,9 +211,11 @@ export class AccountRequestTableComponent { this.statusMessageService.showSuccessToast( `Account request was successfully rejected. Email has been sent to ${accountRequest.email}.`, ); + this.isRejectingAccount[index] = false; }, error: (resp: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(resp.error.message); + this.isRejectingAccount[index] = false; }, }); }, () => {}); diff --git a/src/web/app/components/account-requests-table/account-request-table.module.ts b/src/web/app/components/account-requests-table/account-request-table.module.ts index 2ff431b1021..eb0768663a9 100644 --- a/src/web/app/components/account-requests-table/account-request-table.module.ts +++ b/src/web/app/components/account-requests-table/account-request-table.module.ts @@ -8,6 +8,7 @@ import { RejectWithReasonModalComponent, } from './admin-reject-with-reason-modal/admin-reject-with-reason-modal.component'; import { Pipes } from '../../pipes/pipes.module'; +import { AjaxLoadingModule } from '../ajax-loading/ajax-loading.module'; import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.module'; /** @@ -29,6 +30,7 @@ import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.modul NgbDropdownModule, Pipes, RichTextEditorModule, + AjaxLoadingModule, ], }) export class AccountRequestTableModule { } diff --git a/src/web/app/pages-admin/admin-search-page/__snapshots__/admin-search-page.component.spec.ts.snap b/src/web/app/pages-admin/admin-search-page/__snapshots__/admin-search-page.component.spec.ts.snap index 58f15f5a35d..ded3505ea4b 100644 --- a/src/web/app/pages-admin/admin-search-page/__snapshots__/admin-search-page.component.spec.ts.snap +++ b/src/web/app/pages-admin/admin-search-page/__snapshots__/admin-search-page.component.spec.ts.snap @@ -8,6 +8,8 @@ exports[`AdminSearchPageComponent should snap with a deleted course 1`] = ` emailGenerationService={[Function EmailGenerationService]} instructorService={[Function InstructorService]} instructors={[Function Array]} + isRegeneratingInstructorKeys={[Function Array]} + isRegeneratingStudentKeys={[Function Array]} loadingBarService={[Function LoadingBarService]} searchQuery="" searchService={[Function SearchService]} @@ -339,6 +341,8 @@ exports[`AdminSearchPageComponent should snap with a search key 1`] = ` emailGenerationService={[Function EmailGenerationService]} instructorService={[Function InstructorService]} instructors={[Function Array]} + isRegeneratingInstructorKeys={[Function Array]} + isRegeneratingStudentKeys={[Function Array]} loadingBarService={[Function LoadingBarService]} searchQuery={[Function String]} searchService={[Function SearchService]} @@ -392,6 +396,8 @@ exports[`AdminSearchPageComponent should snap with an expanded instructor table emailGenerationService={[Function EmailGenerationService]} instructorService={[Function InstructorService]} instructors={[Function Array]} + isRegeneratingInstructorKeys={[Function Array]} + isRegeneratingStudentKeys={[Function Array]} loadingBarService={[Function LoadingBarService]} searchQuery="" searchService={[Function SearchService]} @@ -649,6 +655,8 @@ exports[`AdminSearchPageComponent should snap with an expanded student table 1`] emailGenerationService={[Function EmailGenerationService]} instructorService={[Function InstructorService]} instructors={[Function Array]} + isRegeneratingInstructorKeys={[Function Array]} + isRegeneratingStudentKeys={[Function Array]} loadingBarService={[Function LoadingBarService]} searchQuery="" searchService={[Function SearchService]} @@ -955,6 +963,8 @@ exports[`AdminSearchPageComponent should snap with default fields 1`] = ` emailGenerationService={[Function EmailGenerationService]} instructorService={[Function InstructorService]} instructors={[Function Array]} + isRegeneratingInstructorKeys={[Function Array]} + isRegeneratingStudentKeys={[Function Array]} loadingBarService={[Function LoadingBarService]} searchQuery="" searchService={[Function SearchService]} diff --git a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.html b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.html index 8084c47362c..eb4e8c3ad43 100644 --- a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.html +++ b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.html @@ -64,7 +64,7 @@ Reset Google ID
- + @@ -159,7 +159,7 @@ Reset Google ID
- + diff --git a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.spec.ts b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.spec.ts index 143b3ca9d75..da5b794b03f 100644 --- a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.spec.ts +++ b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.spec.ts @@ -607,9 +607,15 @@ describe('AdminSearchPageComponent', () => { component.students = [studentResult]; fixture.detectChanges(); - jest.spyOn(ngbModal, 'open').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + jest.spyOn(ngbModal, 'open').mockReturnValue(mockModalRef as any); jest.spyOn(studentService, 'regenerateStudentKey').mockReturnValue(of({ message: 'success', @@ -669,9 +675,15 @@ describe('AdminSearchPageComponent', () => { component.students = [studentResult]; fixture.detectChanges(); - jest.spyOn(ngbModal, 'open').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + jest.spyOn(ngbModal, 'open').mockReturnValue(mockModalRef as any); jest.spyOn(studentService, 'regenerateStudentKey').mockReturnValue(throwError(() => ({ error: { @@ -698,9 +710,15 @@ describe('AdminSearchPageComponent', () => { component.instructors = [instructorResult]; fixture.detectChanges(); - jest.spyOn(ngbModal, 'open').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + jest.spyOn(ngbModal, 'open').mockReturnValue(mockModalRef as any); jest.spyOn(instructorService, 'regenerateInstructorKey').mockReturnValue(of({ message: 'success', @@ -728,9 +746,15 @@ describe('AdminSearchPageComponent', () => { component.instructors = [instructorResult]; fixture.detectChanges(); - jest.spyOn(ngbModal, 'open').mockImplementation(() => { - return createMockNgbModalRef({}); - }); + const mockModalRef = { + componentInstance: {}, + result: Promise.resolve({}), + dismissed: { + subscribe: jest.fn(), + }, + }; + + jest.spyOn(ngbModal, 'open').mockReturnValue(mockModalRef as any); jest.spyOn(instructorService, 'regenerateInstructorKey').mockReturnValue(throwError(() => ({ error: { diff --git a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.ts b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.ts index 08b577d788c..ede96196a81 100755 --- a/src/web/app/pages-admin/admin-search-page/admin-search-page.component.ts +++ b/src/web/app/pages-admin/admin-search-page/admin-search-page.component.ts @@ -43,6 +43,9 @@ export class AdminSearchPageComponent { accountRequests: AccountRequestTableRowModel[] = []; characterLimit = 100; + isRegeneratingInstructorKeys: boolean[] = []; + isRegeneratingStudentKeys: boolean[] = []; + constructor( private statusMessageService: StatusMessageService, private simpleModalService: SimpleModalService, @@ -83,6 +86,9 @@ export class AdminSearchPageComponent { this.hideAllInstructorsLinks(); this.hideAllStudentsLinks(); + this.isRegeneratingInstructorKeys = new Array(this.instructors.length).fill(false); + this.isRegeneratingStudentKeys = new Array(this.students.length).fill(false); + // prompt user to use more specific terms if search results limit reached const limit: number = ApiConst.SEARCH_QUERY_SIZE_LIMIT; const limitsReached: string[] = []; @@ -222,22 +228,29 @@ export class AdminSearchPageComponent { /** * Regenerates the student's registration key. */ - regenerateStudentKey(student: StudentAccountSearchResult): void { + regenerateStudentKey(student: StudentAccountSearchResult, index: number): void { + this.isRegeneratingStudentKeys[index] = true; const modalContent: string = `Are you sure you want to regenerate the registration key for ${student.name} for the course ${student.courseId}? An email will be sent to the student with all the new course registration and feedback session links.`; const modalRef: NgbModalRef = this.simpleModalService.openConfirmationModal( `Regenerate ${student.name}'s course links?`, SimpleModalType.WARNING, modalContent); + modalRef.dismissed.subscribe(() => { + this.isRegeneratingStudentKeys[index] = false; + }); + modalRef.result.then(() => { this.studentService.regenerateStudentKey(student.courseId, student.email) .subscribe({ next: (resp: RegenerateKey) => { this.statusMessageService.showSuccessToast(resp.message); this.updateDisplayedStudentCourseLinks(student, resp.newRegistrationKey); + this.isRegeneratingStudentKeys[index] = false; }, error: (response: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(response.error.message); + this.isRegeneratingStudentKeys[index] = false; }, }); }, () => {}); @@ -246,22 +259,29 @@ export class AdminSearchPageComponent { /** * Regenerates the instructor's registration key. */ - regenerateInstructorKey(instructor: InstructorAccountSearchResult): void { + regenerateInstructorKey(instructor: InstructorAccountSearchResult, index: number): void { + this.isRegeneratingInstructorKeys[index] = true; const modalContent: string = `Are you sure you want to regenerate the registration key for ${instructor.name} for the course ${instructor.courseId}? An email will be sent to the instructor with all the new course registration and feedback session links.`; const modalRef: NgbModalRef = this.simpleModalService.openConfirmationModal( `Regenerate ${instructor.name}'s course links?`, SimpleModalType.WARNING, modalContent); + modalRef.dismissed.subscribe(() => { + this.isRegeneratingInstructorKeys[index] = false; + }); + modalRef.result.then(() => { this.instructorService.regenerateInstructorKey(instructor.courseId, instructor.email) .subscribe({ next: (resp: RegenerateKey) => { this.statusMessageService.showSuccessToast(resp.message); this.updateDisplayedInstructorCourseLinks(instructor, resp.newRegistrationKey); + this.isRegeneratingInstructorKeys[index] = false; }, error: (response: ErrorMessageOutput) => { this.statusMessageService.showErrorToast(response.error.message); + this.isRegeneratingInstructorKeys[index] = false; }, }); }, () => {}); diff --git a/src/web/app/pages-admin/admin-search-page/admin-search-page.module.ts b/src/web/app/pages-admin/admin-search-page/admin-search-page.module.ts index 6b70a93077a..71925363326 100644 --- a/src/web/app/pages-admin/admin-search-page/admin-search-page.module.ts +++ b/src/web/app/pages-admin/admin-search-page/admin-search-page.module.ts @@ -7,6 +7,7 @@ import { AdminSearchPageComponent } from './admin-search-page.component'; import { AccountRequestTableModule, } from '../../components/account-requests-table/account-request-table.module'; +import { AjaxLoadingModule } from '../../components/ajax-loading/ajax-loading.module'; import { Pipes } from '../../pipes/pipes.module'; const routes: Routes = [ @@ -33,6 +34,7 @@ const routes: Routes = [ AccountRequestTableModule, RouterModule.forChild(routes), Pipes, + AjaxLoadingModule, ], }) export class AdminSearchPageModule { }