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

[#13105] Admin managing account requests: use spinners to indicate actions in progress #13135

Merged
merged 7 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ exports[`AccountRequestTableComponent should display account requests with no re
<tm-account-request-table
accountRequests={[Function Array]}
accountService={[Function AccountService]}
isApprovingAccount={[Function Array]}
isRejectingAccount={[Function Array]}
isResettingAccount={[Function Array]}
ngbModal={[Function _NgbModal]}
searchString=""
simpleModalService={[Function SimpleModalService]}
Expand Down Expand Up @@ -267,6 +270,9 @@ exports[`AccountRequestTableComponent should display account requests with reset
<tm-account-request-table
accountRequests={[Function Array]}
accountService={[Function AccountService]}
isApprovingAccount={[Function Array]}
isRejectingAccount={[Function Array]}
isResettingAccount={[Function Array]}
ngbModal={[Function _NgbModal]}
searchString={[Function String]}
simpleModalService={[Function SimpleModalService]}
Expand Down Expand Up @@ -592,6 +598,9 @@ exports[`AccountRequestTableComponent should snap with an expanded account reque
<tm-account-request-table
accountRequests={[Function Array]}
accountService={[Function AccountService]}
isApprovingAccount={[Function Array]}
isRejectingAccount={[Function Array]}
isResettingAccount={[Function Array]}
ngbModal={[Function _NgbModal]}
searchString=""
simpleModalService={[Function SimpleModalService]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@
<i class="fa-solid fa-eye"></i>
</a>
</div>
<button id="approve-account-request-{{i}}" class="btn btn-success" [disabled]="!accountRequest.status || accountRequest.status === 'APPROVED' || accountRequest.status === 'REGISTERED'" (click)="$event.stopPropagation(); approveAccountRequest(accountRequest)">Approve</button>
<button id="approve-account-request-{{i}}" class="btn btn-success" [disabled]="!accountRequest.status || accountRequest.status === 'APPROVED' || accountRequest.status === 'REGISTERED' || isApprovingAccount[i]" (click)="$event.stopPropagation(); approveAccountRequest(accountRequest, i)"> <tm-ajax-loading *ngIf="isApprovingAccount[i]"></tm-ajax-loading>Approve</button>
<span ngbDropdown container="body">
<button id="reject-account-request-{{i}}" type="button" class="btn btn-warning" [disabled]="!accountRequest.status || accountRequest.status === 'REGISTERED' || accountRequest.status === 'APPROVED' || accountRequest.status === 'REJECTED'" ngbDropdownToggle> Reject </button>
<button id="reject-account-request-{{i}}" type="button" class="btn btn-warning" [disabled]="!accountRequest.status || accountRequest.status === 'REGISTERED' || accountRequest.status === 'APPROVED' || accountRequest.status === 'REJECTED' || isRejectingAccount[i]" ngbDropdownToggle> <tm-ajax-loading *ngIf="isRejectingAccount[i]"></tm-ajax-loading> Reject </button>
<div ngbDropdownMenu (click)="$event.stopPropagation()">
<button id="reject-request-{{i}}" class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequest(accountRequest)"> Reject </button>
<button id="reject-request-with-reason-{{i}}" class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequestWithReason(accountRequest)"> Reject With Reason </button>
<button id="reject-request-{{i}}" class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequest(accountRequest, i)"> Reject </button>
<button id="reject-request-with-reason-{{i}}" class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequestWithReason(accountRequest, i)"> Reject With Reason </button>
</div>
</span>
<div *ngIf="searchString" class="ngb-tooltip-class" [ngbTooltip]="accountRequest.registeredAtText && 'Account requests of registered instructors cannot be deleted'" placement="top">
<button id="reset-account-request-{{i}}" class="btn btn-primary" [disabled]="!accountRequest.registeredAtText" (click)="$event.stopPropagation(); resetAccountRequest(accountRequest);">Reset</button>
<button id="reset-account-request-{{i}}" class="btn btn-primary" [disabled]="!accountRequest.registeredAtText || isResettingAccount[i]" (click)="$event.stopPropagation(); resetAccountRequest(accountRequest, i);"> <tm-ajax-loading *ngIf="isResettingAccount[i]"></tm-ajax-loading>Reset</button>
</div>
</div>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand All @@ -103,31 +108,40 @@ 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
<strong>${accountRequest.name}</strong> with email <strong>${accountRequest.email}</strong> from
<strong>${accountRequest.instituteAndCountry}</strong>?
An email with the account registration link will also be sent to the instructor.`;
const modalRef: NgbModalRef = this.simpleModalService.openConfirmationModal(
`Reset account request for <strong>${accountRequest.name}</strong>?`, SimpleModalType.WARNING, modalContent);

modalRef.dismissed.subscribe(() => {
this.isResettingAccount[index] = false;
});

modalRef.result.then(() => {
this.accountService.resetAccountRequest(accountRequest.id)
.subscribe({
next: () => {
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;
},
});
}, () => {});
Expand Down Expand Up @@ -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)
Expand All @@ -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;
},
});
}, () => {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -29,6 +30,7 @@ import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.modul
NgbDropdownModule,
Pipes,
RichTextEditorModule,
AjaxLoadingModule,
],
})
export class AccountRequestTableModule { }
Original file line number Diff line number Diff line change
Expand Up @@ -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]}
Expand Down Expand Up @@ -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]}
Expand Down Expand Up @@ -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]}
Expand Down Expand Up @@ -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]}
Expand Down Expand Up @@ -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]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<i class="fas fa-sync"></i> Reset Google ID
</a>
<br>
<button id="regenerate-instructor-key-{{i}}" class="btn btn-danger" (click)="instructor.showLinks = !instructor.showLinks; regenerateInstructorKey(instructor);">Regenerate key</button>
<button id="regenerate-instructor-key-{{i}}" class="btn btn-danger" [disabled]="isRegeneratingInstructorKeys[i]" (click)="instructor.showLinks = !instructor.showLinks; regenerateInstructorKey(instructor, i);"><tm-ajax-loading *ngIf="isRegeneratingInstructorKeys[i]"></tm-ajax-loading>Regenerate key</button>
</td>
</tr>
<tr *ngIf="instructor.showLinks">
Expand Down Expand Up @@ -159,7 +159,7 @@
<i class="fas fa-sync"></i> Reset Google ID
</a>
<br>
<button id="regenerate-student-key-{{i}}" class="btn btn-danger" (click)="student.showLinks = !student.showLinks; regenerateStudentKey(student);">Regenerate key</button>
<button id="regenerate-student-key-{{i}}" class="btn btn-danger" [disabled]="isRegeneratingStudentKeys[i]" (click)="student.showLinks = !student.showLinks; regenerateStudentKey(student, i);"><tm-ajax-loading *ngIf="isRegeneratingStudentKeys[i]"></tm-ajax-loading>Regenerate key</button>
</td>
</tr>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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: {
Expand All @@ -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',
Expand Down Expand Up @@ -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: {
Expand Down
Loading
Loading