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

[#11878] Add Edit and Approve Account Requests functionality #12975

Merged
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
@@ -1,11 +1,13 @@
import { AccountRequestStatus } from 'src/web/types/api-output';

/**
* Model for the row entries in the account requests table.
*/
export interface AccountRequestTableRowModel {
id: string;
name: string;
email: string;
status: string;
status: AccountRequestStatus;
instituteAndCountry: string;
createdAtText: string;
registeredAtText: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
</td>
<td class="align-middle">
<div class="d-flex flex-row align-items-center justify-content-center gap-2">
<div>
<a id="edit-account-request-{{i}}" href="javascript:;" (click)="$event.stopPropagation(); editAccountRequest(accountRequest);">
<i class="fa-solid fa-pen"></i>
</a>
</div>
<div class="ngb-tooltip-class" [ngbTooltip]="accountRequest.registeredAtText && 'Account requests of registered instructors cannot be deleted'">
<a id="delete-account-request-{{i}}" href="javascript:;" (click)="$event.stopPropagation(); deleteAccountRequest(accountRequest)">
<i class="fa-solid fa-trash"></i>
Expand All @@ -64,7 +69,8 @@
<div *ngIf="searchString" class="ngb-tooltip-class" [ngbTooltip]="accountRequest.registeredAtText && 'Account requests of registered instructors cannot be deleted'">
<button id="reset-account-request-{{i}}" class="btn btn-primary" [disabled]="!accountRequest.registeredAtText" (click)="$event.stopPropagation(); resetAccountRequest(accountRequest);">Reset</button>
</div>
</div>
<button id="approve-account-request-{{i}}" class="btn btn-success" [disabled]="accountRequest.registeredAtText || accountRequest.status === 'APPROVED'" (click)="$event.stopPropagation(); approveAccountRequest(accountRequest)">Approve</button>
</div>
</td>
</tr>
<tr *ngIf="accountRequest.showLinks && searchString">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Component, Input } from '@angular/core';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { AccountRequestTableRowModel } from './account-request-table-model';
import { EditRequestModalComponent } from './admin-edit-request-modal/admin-edit-request-modal.component';
import { AccountService } from '../../../services/account.service';
import { SimpleModalService } from '../../../services/simple-modal.service';
import { StatusMessageService } from '../../../services/status-message.service';
import { MessageOutput } from '../../../types/api-output';
import { AccountRequest, AccountRequestStatus, MessageOutput } from '../../../types/api-output';
import { ErrorMessageOutput } from '../../error-message-output';
import { SimpleModalType } from '../simple-modal/simple-modal-type';
import { collapseAnim } from '../teammates-common/collapse-anim';
Expand All @@ -31,6 +32,7 @@ export class AccountRequestTableComponent {
private statusMessageService: StatusMessageService,
private simpleModalService: SimpleModalService,
private accountService: AccountService,
private ngbModal: NgbModal,
) {}

/**
Expand All @@ -51,6 +53,48 @@ export class AccountRequestTableComponent {
}
}

editAccountRequest(accountRequest: AccountRequestTableRowModel): void {
const modalRef: NgbModalRef = this.ngbModal.open(EditRequestModalComponent);
modalRef.componentInstance.accountRequestName = accountRequest.name;
modalRef.componentInstance.accountRequestEmail = accountRequest.email;
modalRef.componentInstance.accountRequestInstitution = accountRequest.instituteAndCountry;
modalRef.componentInstance.accountRequestComments = accountRequest.comments;

modalRef.result.then(() => {
this.accountService.editAccountRequest(
accountRequest.id,
modalRef.componentInstance.accountRequestName,
modalRef.componentInstance.accountRequestEmail,
modalRef.componentInstance.accountRequestInstitution,
accountRequest.status,
modalRef.componentInstance.accountRequestComments)
.subscribe({
next: (resp: AccountRequest) => {
accountRequest.comments = resp.comments ?? '';
accountRequest.name = resp.name;
accountRequest.email = resp.email;
accountRequest.instituteAndCountry = resp.institute;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
});
}

approveAccountRequest(accountRequest: AccountRequestTableRowModel): void {
this.accountService.approveAccountRequest(accountRequest.id, accountRequest.name,
accountRequest.email, accountRequest.instituteAndCountry)
.subscribe({
next: () => {
accountRequest.status = AccountRequestStatus.APPROVED;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
}

resetAccountRequest(accountRequest: AccountRequestTableRowModel): void {
const modalContent = `Are you sure you want to reset the account request for
<strong>${accountRequest.name}</strong> with email <strong>${accountRequest.email}</strong> from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbTooltipModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { AccountRequestTableComponent } from './account-request-table.component';
import { EditRequestModalComponent } from './admin-edit-request-modal/admin-edit-request-modal.component';
import { Pipes } from '../../pipes/pipes.module';
import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.module';

/**
* Module for account requests table.
*/
@NgModule({
declarations: [
AccountRequestTableComponent,
EditRequestModalComponent,
],
exports: [
AccountRequestTableComponent,
Expand All @@ -21,6 +24,7 @@ import { Pipes } from '../../pipes/pipes.module';
NgbTooltipModule,
NgbDropdownModule,
Pipes,
RichTextEditorModule,
],
})
export class AccountRequestTableModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Result of {@link EditRequestModalComponent}

Check warning on line 2 in src/web/app/components/account-requests-table/admin-edit-request-modal/admin-edit-request-modal-model.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

The type 'EditRequestModalComponent' is undefined

Check warning on line 2 in src/web/app/components/account-requests-table/admin-edit-request-modal/admin-edit-request-modal-model.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

The type 'EditRequestModalComponent' is undefined
*/
export interface EditRequestModalComponentResult {
accountRequestName: string;
accountRequestEmail: string;
accountRequestInstitution: string;
accountRequestComment: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">
<div>Edit Account Request for {{ accountRequestName }}</div>
</h5>
<button type="button" class="btn-close" (click)="activeModal.dismiss()"></button>
</div>
<div id="reject-account-request-modal" class="modal-body">
<div>
<label><b>Name:</b></label>
<input id="request-name" type="text" class="form-control" value="{{ accountRequestName }}" (input)="accountRequestName = $event.target.value">
</div>
<div>
<label><b>Email:</b></label>
<input id="request-email" type="text" class="form-control" value="{{ accountRequestEmail }}" (input)="accountRequestEmail = $event.target.value">
</div>
<div>
<label><b>Institution, Country:</b></label>
<input id="request-institution" type="text" class="form-control" value="{{ accountRequestInstitution }}" (input)="accountRequestInstitution = $event.target.value">
</div>
<div class="row">
<div class="col-12">
<div>
<label><b>Comments:</b></label>
</div>
<div class="form-group">
<textarea id="request-comments" class="form-control" rows="5" value="{{ accountRequestComments }}" (input)="accountRequestComments = $event.target.value"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="activeModal.dismiss()">Cancel</button>
<button id="btn-confirm-reject-request" type="button" class="btn btn-primary" (click)="edit()">
Save
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, Input } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { EditRequestModalComponentResult } from './admin-edit-request-modal-model';

/**
* Modal to select reject account requests with reason.
*/
@Component({
selector: 'tm-edit-request-modal',
templateUrl: './admin-edit-request-modal.component.html',
styleUrls: ['./admin-edit-request-modal.component.scss'],
})

export class EditRequestModalComponent {

@Input()
accountRequestName: string = '';
@Input()
accountRequestEmail: string = '';
@Input()
accountRequestInstitution: string = '';
@Input()
accountRequestComments: string = '';

constructor(public activeModal: NgbActiveModal) {}

/**
* Fires the edit event.
*/
edit(): void {
const result: EditRequestModalComponentResult = {
accountRequestName: this.accountRequestName,
accountRequestEmail: this.accountRequestEmail,
accountRequestInstitution: this.accountRequestInstitution,
accountRequestComment: this.accountRequestComments,
};

this.activeModal.close(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,16 @@ exports[`AdminSearchPageComponent should snap with an expanded account requests
<div
class="d-flex flex-row align-items-center justify-content-center gap-2"
>
<div>
<a
href="javascript:;"
id="edit-account-request-0"
>
<i
class="fa-solid fa-pen"
/>
</a>
</div>
<div
class="ngb-tooltip-class"
>
Expand All @@ -529,6 +539,12 @@ exports[`AdminSearchPageComponent should snap with an expanded account requests
/>
</a>
</div>
<button
class="btn btn-success"
id="approve-account-request-0"
>
Approve
</button>
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,4 @@
</div>
</div>

<tm-account-request-table *ngIf="accountRequests.length" [accountRequests]="accountRequests" [searchString]="searchQuery"></tm-account-request-table>
<tm-account-request-table *ngIf="accountRequests.length" [accountRequests]="accountRequests" [searchString]="searchString"></tm-account-request-table>
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ describe('AdminSearchPageComponent', () => {
it('should show account request links when expand all button clicked', () => {
const accountRequestResult: AccountRequestTableRowModel = DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT;
component.accountRequests = [accountRequestResult];
component.searchQuery = 'test'; // To show the account request table
component.searchString = 'test'; // To show the account request table
fixture.detectChanges();

const button: any = fixture.debugElement.nativeElement.querySelector('#show-account-request-links');
Expand Down Expand Up @@ -961,7 +961,7 @@ describe('AdminSearchPageComponent', () => {
it('should show error message when resetting account request is unsuccessful', () => {
component.accountRequests = [DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT];
component.accountRequests[0].registeredAtText = 'Wed, 09 Feb 2022, 10:23 AM +00:00';
component.searchQuery = 'test';
component.searchString = 'test';
fixture.detectChanges();

jest.spyOn(ngbModal, 'open').mockImplementation(() => {
Expand All @@ -988,7 +988,7 @@ describe('AdminSearchPageComponent', () => {
it('should show success message when resetting account request is successful', () => {
component.accountRequests = [DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT];
component.accountRequests[0].registeredAtText = 'Wed, 09 Feb 2022, 10:23 AM +00:00';
component.searchQuery = 'test';
component.searchString = 'test';
fixture.detectChanges();

jest.spyOn(ngbModal, 'open').mockImplementation(() => {
Expand Down
40 changes: 39 additions & 1 deletion src/web/services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
MessageOutput,
AccountRequestStatus,
} from '../types/api-output';
import { AccountCreateRequest } from '../types/api-request';
import { AccountCreateRequest, AccountRequestUpdateRequest } from '../types/api-request';

/**
* Handles account related logic provision
Expand Down Expand Up @@ -95,6 +95,44 @@ export class AccountService {
return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_RESET, paramMap);
}

/**
* Approves account request by calling API
*/
approveAccountRequest(id: string, name: string, email: string, institute: string)
: Observable<MessageOutput> {
const paramMap: Record<string, string> = {
id,
};
const accountReqUpdateRequest : AccountRequestUpdateRequest = {
name,
email,
institute,
status: AccountRequestStatus.APPROVED,
};

return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_REQUEST, paramMap, accountReqUpdateRequest);
}

/**
* Edits an account request by calling API.
*/
editAccountRequest(id: string, name: string, email: string, institute: string,
status: AccountRequestStatus, comments: string)
: Observable<AccountRequest> {
const paramMap: Record<string, string> = {
id,
};
const accountReqUpdateRequest : AccountRequestUpdateRequest = {
name,
email,
institute,
status,
comments,
};

return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_REQUEST, paramMap, accountReqUpdateRequest);
}

/**
* Gets an account by calling API.
*/
Expand Down
5 changes: 3 additions & 2 deletions src/web/services/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ResourceEndpoints } from '../types/api-const';
import {
AccountRequest,
AccountRequests,
AccountRequestStatus,
Course, FeedbackSession,
FeedbackSessions,
Instructor,
Expand Down Expand Up @@ -307,7 +308,7 @@ export class SearchService {
registeredAtText: '',
registrationLink: '',
showLinks: false,
status: '',
status: AccountRequestStatus.PENDING,
comments: '',
};

Expand Down Expand Up @@ -474,7 +475,7 @@ export interface AccountRequestSearchResult {
id: string;
name: string;
email: string;
status: string;
status: AccountRequestStatus;
institute: string;
createdAtText: string;
registeredAtText: string | null;
Expand Down
Loading