69115: ClaimedTaskDataService and components refactoring

This commit is contained in:
Kristof De Langhe
2020-02-27 13:45:54 +01:00
parent 18b9a41fe0
commit b9c050c19c
17 changed files with 169 additions and 118 deletions

View File

@@ -52,8 +52,7 @@ describe('ClaimedTaskDataService', () => {
options.headers = headers;
});
describe('approveTask', () => {
describe('submitTask', () => {
it('should call postToEndpoint method', () => {
const scopeId = '1234';
const body = {
@@ -63,33 +62,13 @@ describe('ClaimedTaskDataService', () => {
spyOn(service, 'postToEndpoint');
requestService.uriEncodeBody.and.returnValue(body);
service.approveTask(scopeId);
expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, body, scopeId, options);
});
});
describe('rejectTask', () => {
it('should call postToEndpoint method', () => {
const scopeId = '1234';
const reason = 'test reject';
const body = {
submit_reject: 'true',
reason
};
spyOn(service, 'postToEndpoint');
requestService.uriEncodeBody.and.returnValue(body);
service.rejectTask(reason, scopeId);
service.submitTask(scopeId, body);
expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, body, scopeId, options);
});
});
describe('returnToPoolTask', () => {
it('should call deleteById method', () => {
const scopeId = '1234';

View File

@@ -35,7 +35,6 @@ export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
*
* @param {RequestService} requestService
* @param {RemoteDataBuildService} rdbService
* @param {NormalizedObjectBuildService} linkService
* @param {Store<CoreState>} store
* @param {ObjectCacheService} objectCache
* @param {HALEndpointService} halService
@@ -56,35 +55,16 @@ export class ClaimedTaskDataService extends TasksService<ClaimedTask> {
}
/**
* Make a request to approve the given task
* Make a request for the given task
*
* @param scopeId
* The task id
* @param body
* The request body
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public approveTask(scopeId: string): Observable<ProcessTaskResponse> {
const body = {
submit_approve: 'true'
};
return this.postToEndpoint(this.linkPath, this.requestService.uriEncodeBody(body), scopeId, this.makeHttpOptions());
}
/**
* Make a request to reject the given task
*
* @param reason
* The reason of reject
* @param scopeId
* The task id
* @return {Observable<ProcessTaskResponse>}
* Emit the server response
*/
public rejectTask(reason: string, scopeId: string): Observable<ProcessTaskResponse> {
const body = {
submit_reject: 'true',
reason
};
public submitTask(scopeId: string, body: any): Observable<ProcessTaskResponse> {
return this.postToEndpoint(this.linkPath, this.requestService.uriEncodeBody(body), scopeId, this.makeHttpOptions());
}

View File

@@ -1,11 +1,24 @@
import { EventEmitter, Input, Output } from '@angular/core';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
import { WorkflowTaskOptions } from '../workflow-task-options.model';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
/**
* Abstract component for rendering a claimed task's action
* To create a child-component for a new option:
* - Make sure the option is defined in the WorkflowTaskOptions enum
* - Set the "option" of the component to the enum value
* - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value
* - Optionally overwrite createBody if the request body requires more than just the option
*/
export abstract class ClaimedTaskActionsAbstractComponent {
/**
* The workflow task option the child component represents
*/
abstract option: WorkflowTaskOptions;
/**
* The Claimed Task to display an action for
*/
@@ -21,8 +34,30 @@ export abstract class ClaimedTaskActionsAbstractComponent {
*/
processing$ = new BehaviorSubject<boolean>(false);
constructor(protected claimedTaskService: ClaimedTaskDataService) {
}
/**
* Method called when the action's button is clicked
* Create a request body for submitting the task
* Overwrite this method in the child component if the body requires more than just the option
*/
abstract process();
createbody(): any {
return {
[this.option]: 'true'
};
}
/**
* Submit the task for this option
* While the task is submitting, processing$ is set to true and processCompleted emits the response's status when
* completed
*/
submitTask() {
this.processing$.next(true);
this.claimedTaskService.submitTask(this.object.id, this.createbody())
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
}
}

View File

@@ -2,7 +2,7 @@
[className]="'btn btn-success'"
ngbTooltip="{{'submission.workflow.tasks.claimed.approve_help' | translate}}"
[disabled]="processing$ | async"
(click)="process()">
(click)="submitTask()">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processing$ | async)"><i class="fa fa-thumbs-up"></i> {{'submission.workflow.tasks.claimed.approve' | translate}}</span>
</button>

View File

@@ -16,7 +16,7 @@ let fixture: ComponentFixture<ClaimedTaskActionsApproveComponent>;
describe('ClaimedTaskActionsApproveComponent', () => {
const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' });
const claimedTaskService = jasmine.createSpyObj('claimedTaskService', {
approveTask: observableOf(new ProcessTaskResponse(true))
submitTask: observableOf(new ProcessTaskResponse(true))
});
beforeEach(async(() => {
@@ -61,13 +61,27 @@ describe('ClaimedTaskActionsApproveComponent', () => {
expect(span).toBeDefined();
});
it('should emit a successful processCompleted event', () => {
spyOn(component.processCompleted, 'emit');
describe('submitTask', () => {
let expectedBody;
component.process();
fixture.detectChanges();
beforeEach(() => {
spyOn(component.processCompleted, 'emit');
expect(component.processCompleted.emit).toHaveBeenCalled();
expectedBody = {
[component.option]: 'true'
};
component.submitTask();
fixture.detectChanges();
});
it('should call claimedTaskService\'s submitTask with the expected body', () => {
expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody)
});
it('should emit a successful processCompleted event', () => {
expect(component.processCompleted.emit).toHaveBeenCalledWith(true);
});
});
});

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
import { WorkflowTaskOptions } from '../workflow-task-options.model';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
@rendersWorkflowTaskOption('submit_approve')
@rendersWorkflowTaskOption(WorkflowTaskOptions.Approve)
@Component({
selector: 'ds-claimed-task-actions-approve',
styleUrls: ['./claimed-task-actions-approve.component.scss'],
@@ -14,20 +14,12 @@ import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data
* Component for displaying and processing the approve action on a workflow task item
*/
export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstractComponent {
/**
* This component represents the approve option
*/
option = WorkflowTaskOptions.Approve;
constructor(protected claimedTaskService: ClaimedTaskDataService) {
super();
}
/**
* Approve the task
*/
process() {
this.processing$.next(true);
this.claimedTaskService.approveTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
super(claimedTaskService);
}
}

View File

@@ -16,7 +16,7 @@ import { RequestService } from '../../../core/data/request.service';
import { SearchService } from '../../../core/shared/search/search.service';
import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
import { WORKFLOW_TASK_OPTION_RETURN } from './return-to-pool/claimed-task-actions-return-to-pool.component';
import { WorkflowTaskOptions } from './workflow-task-options.model';
/**
* This component represents actions related to ClaimedTask object.
@@ -47,7 +47,7 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
* The option used to render the "return to pool" component
* Every claimed task contains this option
*/
public returnToPoolOption = WORKFLOW_TASK_OPTION_RETURN;
public returnToPoolOption = WorkflowTaskOptions.ReturnToPool;
/**
* Initialize instance variables

View File

@@ -6,6 +6,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskActionsEditMetadataComponent } from './claimed-task-actions-edit-metadata.component';
import { MockTranslateLoader } from '../../../mocks/mock-translate-loader';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
let component: ClaimedTaskActionsEditMetadataComponent;
let fixture: ComponentFixture<ClaimedTaskActionsEditMetadataComponent>;
@@ -23,6 +24,9 @@ describe('ClaimedTaskActionsEditMetadataComponent', () => {
}
})
],
providers: [
{ provide: ClaimedTaskDataService, useValue: {} }
],
declarations: [ClaimedTaskActionsEditMetadataComponent],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedTaskActionsEditMetadataComponent, {

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
import { WorkflowTaskOptions } from '../workflow-task-options.model';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
@rendersWorkflowTaskOption('submit_edit_metadata')
@rendersWorkflowTaskOption(WorkflowTaskOptions.EditMetadata)
@Component({
selector: 'ds-claimed-task-actions-edit-metadata',
styleUrls: ['./claimed-task-actions-edit-metadata.component.scss'],
@@ -14,7 +14,12 @@ import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data
* Component for displaying the edit metadata action on a workflow task item
*/
export class ClaimedTaskActionsEditMetadataComponent extends ClaimedTaskActionsAbstractComponent {
process() {
// Nothing needs to happen for the edit-metadata button, it simply renders a link to another page
/**
* This component represents the edit metadata option
*/
option = WorkflowTaskOptions.EditMetadata;
constructor(protected claimedTaskService: ClaimedTaskDataService) {
super(claimedTaskService);
}
}

View File

@@ -21,7 +21,7 @@
<div class="alert alert-info" role="alert">
{{'submission.workflow.tasks.claimed.reject.reason.info' | translate}}
</div>
<form (ngSubmit)="process(rejectModal);" [formGroup]="rejectForm" >
<form (ngSubmit)="submitTask(rejectModal);" [formGroup]="rejectForm" >
<textarea style="width: 100%"
formControlName="reason"
rows="4"

View File

@@ -21,7 +21,7 @@ let modalService: NgbModal;
describe('ClaimedTaskActionsRejectComponent', () => {
const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' });
const claimedTaskService = jasmine.createSpyObj('claimedTaskService', {
rejectTask: observableOf(new ProcessTaskResponse(true))
submitTask: observableOf(new ProcessTaskResponse(true))
});
beforeEach(async(() => {
@@ -91,19 +91,34 @@ describe('ClaimedTaskActionsRejectComponent', () => {
component.modalRef.close()
});
it('should call processCompleted on form submit', () => {
spyOn(component.processCompleted, 'emit');
describe('on form submit', () => {
let expectedBody;
const btn = fixture.debugElement.query(By.css('.btn-danger'));
btn.nativeElement.click();
fixture.detectChanges();
beforeEach(() => {
spyOn(component.processCompleted, 'emit');
expect(component.modalRef).toBeDefined();
expectedBody = {
[component.option]: 'true',
reason: null
};
const form = ((document as any).querySelector('form'));
form.dispatchEvent(new Event('ngSubmit'));
fixture.detectChanges();
const btn = fixture.debugElement.query(By.css('.btn-danger'));
btn.nativeElement.click();
fixture.detectChanges();
expect(component.processCompleted.emit).toHaveBeenCalled();
expect(component.modalRef).toBeDefined();
const form = ((document as any).querySelector('form'));
form.dispatchEvent(new Event('ngSubmit'));
fixture.detectChanges();
});
it('should call claimedTaskService\'s submitTask with the expected body', () => {
expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody)
});
it('should emit a successful processCompleted event', () => {
expect(component.processCompleted.emit).toHaveBeenCalledWith(true);
});
});
});

View File

@@ -1,13 +1,13 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
import { WorkflowTaskOptions } from '../workflow-task-options.model';
@rendersWorkflowTaskOption('submit_reject')
@rendersWorkflowTaskOption(WorkflowTaskOptions.Reject)
@Component({
selector: 'ds-claimed-task-actions-reject',
styleUrls: ['./claimed-task-actions-reject.component.scss'],
@@ -18,10 +18,9 @@ import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-deco
*/
export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstractComponent implements OnInit {
/**
* An event fired when a reject action is confirmed.
* Event's payload equals to reject reason.
* This component represents the edit metadata option
*/
@Output() reject: EventEmitter<string> = new EventEmitter<string>();
option = WorkflowTaskOptions.Reject;
/**
* The reject form group
@@ -43,7 +42,7 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac
constructor(protected claimedTaskService: ClaimedTaskDataService,
private formBuilder: FormBuilder,
private modalService: NgbModal) {
super();
super(claimedTaskService);
}
/**
@@ -56,17 +55,20 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac
}
/**
* Reject the task
* Create the request body for rejecting a workflow task
* Includes the reason from the form
*/
process() {
this.processing$.next(true);
createbody(): any {
const reason = this.rejectForm.get('reason').value;
return Object.assign(super.createbody(), { reason });
}
/**
* Submit a reject option for the task
*/
submitTask() {
this.modalRef.close('Send Button');
this.claimedTaskService.rejectTask(reason, this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
super.submitTask();
}
/**

View File

@@ -2,7 +2,7 @@
[className]="'btn btn-secondary'"
ngbTooltip="{{'submission.workflow.tasks.claimed.return_help' | translate}}"
[disabled]="processing$ | async"
(click)="process()">
(click)="submitTask()">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processing$ | async)"><i class="fa fa-undo"></i> {{'submission.workflow.tasks.claimed.return' | translate}}</span>
</button>

View File

@@ -61,13 +61,21 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => {
expect(span).toBeDefined();
});
it('should emit a successful processCompleted event', () => {
spyOn(component.processCompleted, 'emit');
describe('submitTask', () => {
beforeEach(() => {
spyOn(component.processCompleted, 'emit');
component.process();
fixture.detectChanges();
component.submitTask();
fixture.detectChanges();
});
expect(component.processCompleted.emit).toHaveBeenCalled();
it('should call claimedTaskService\'s returnToPoolTask', () => {
expect(claimedTaskService.returnToPoolTask).toHaveBeenCalledWith(object.id)
});
it('should emit a successful processCompleted event', () => {
expect(component.processCompleted.emit).toHaveBeenCalledWith(true);
});
});
});

View File

@@ -1,12 +1,11 @@
import { Component } from '@angular/core';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
import { WorkflowTaskOptions } from '../workflow-task-options.model';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
export const WORKFLOW_TASK_OPTION_RETURN = 'return_to_pool';
@rendersWorkflowTaskOption(WORKFLOW_TASK_OPTION_RETURN)
@rendersWorkflowTaskOption(WorkflowTaskOptions.ReturnToPool)
@Component({
selector: 'ds-claimed-task-actions-return-to-pool',
styleUrls: ['./claimed-task-actions-return-to-pool.component.scss'],
@@ -16,15 +15,19 @@ export const WORKFLOW_TASK_OPTION_RETURN = 'return_to_pool';
* Component for displaying and processing the return to pool action on a workflow task item
*/
export class ClaimedTaskActionsReturnToPoolComponent extends ClaimedTaskActionsAbstractComponent {
/**
* This component represents the edit metadata option
*/
option = WorkflowTaskOptions.ReturnToPool;
constructor(protected claimedTaskService: ClaimedTaskDataService) {
super();
super(claimedTaskService);
}
/**
* Return task to pool
* Submit a return to pool option for the task
*/
process() {
submitTask() {
this.processing$.next(true);
this.claimedTaskService.returnToPoolTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {

View File

@@ -7,6 +7,7 @@ import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component';
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
describe('ClaimedTaskActionsLoaderComponent', () => {
let comp: ClaimedTaskActionsLoaderComponent;
@@ -20,7 +21,10 @@ describe('ClaimedTaskActionsLoaderComponent', () => {
imports: [TranslateModule.forRoot()],
declarations: [ClaimedTaskActionsLoaderComponent, ClaimedTaskActionsEditMetadataComponent, ClaimedTaskActionsDirective],
schemas: [NO_ERRORS_SCHEMA],
providers: [ComponentFactoryResolver]
providers: [
{ provide: ClaimedTaskDataService, useValue: {} },
ComponentFactoryResolver
]
}).overrideComponent(ClaimedTaskActionsLoaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,

View File

@@ -0,0 +1,10 @@
/**
* An enum listing all possible workflow task options
* Used to render components for the options and building request bodies
*/
export enum WorkflowTaskOptions {
Approve = 'submit_approve',
Reject = 'submit_reject',
EditMetadata = 'submit_edit_metadata',
ReturnToPool = 'return_to_pool'
}