68954: Display claimed task actions depending on config from REST API

Conflicts:
	src/app/core/core.module.ts
	src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html
This commit is contained in:
Kristof De Langhe
2020-02-24 17:13:12 +01:00
parent 01231ef105
commit 305c4ce882
22 changed files with 452 additions and 142 deletions

View File

@@ -142,6 +142,8 @@ import { PoolTask } from './tasks/models/pool-task-object.model';
import { TaskObject } from './tasks/models/task-object.model';
import { PoolTaskDataService } from './tasks/pool-task-data.service';
import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
import { WorkflowActionDataService } from './data/workflow-action-data.service';
import { NormalizedWorkflowAction } from './tasks/models/normalized-workflow-action-object.model';
/**
* When not in production, endpoint responses can be mocked for testing purposes
@@ -257,6 +259,7 @@ const PROVIDERS = [
LookupRelationService,
LicenseDataService,
ItemTypeDataService,
WorkflowActionDataService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
@@ -305,6 +308,7 @@ export const models =
ItemType,
ExternalSource,
ExternalSourceEntry,
NormalizedWorkflowAction
];
@NgModule({

View File

@@ -0,0 +1,40 @@
import { DataService } from './data.service';
import { WorkflowAction } from '../tasks/models/workflow-action-object.model';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { FindListOptions } from './request.models';
import { Observable } from 'rxjs/internal/Observable';
import { Injectable } from '@angular/core';
/**
* A service responsible for fetching/sending data from/to the REST API on the workflowactions endpoint
*/
@Injectable()
export class WorkflowActionDataService extends DataService<WorkflowAction> {
protected linkPath = 'workflowactions';
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<WorkflowAction>) {
super();
}
getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
}

View File

@@ -0,0 +1,23 @@
import { autoserialize, inheritSerialization } from 'cerialize';
import { mapsTo } from '../../cache/builders/build-decorators';
import { WorkflowAction } from './workflow-action-object.model';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
/**
* A normalized model class for a WorkflowAction
*/
@mapsTo(WorkflowAction)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedWorkflowAction extends NormalizedDSpaceObject<WorkflowAction> {
/**
* The workflow action's identifier
*/
@autoserialize
id: string;
/**
* The options available for this workflow action
*/
@autoserialize
options: string[];
}

View File

@@ -0,0 +1,19 @@
import { ResourceType } from '../../shared/resource-type';
import { DSpaceObject } from '../../shared/dspace-object.model';
/**
* A model class for a WorkflowAction
*/
export class WorkflowAction extends DSpaceObject {
static type = new ResourceType('workflowaction');
/**
* The workflow action's identifier
*/
id: string;
/**
* The options available for this workflow action
*/
options: string[];
}

View File

@@ -0,0 +1,28 @@
import { EventEmitter, Input, Output } from '@angular/core';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
/**
* Abstract component for rendering a claimed task's action
*/
export abstract class ClaimedTaskActionsAbstractComponent {
/**
* The Claimed Task to display an action for
*/
@Input() object: ClaimedTask;
/**
* Emits the success or failure of a processed action
*/
@Output() processCompleted: EventEmitter<boolean> = new EventEmitter<boolean>();
/**
* A boolean representing if the operation is pending
*/
processing$ = new BehaviorSubject<boolean>(false);
/**
* Method called when the action's button is clicked
*/
abstract process();
}

View File

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

@@ -1,32 +1,33 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
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 { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
@rendersWorkflowTaskOption('submit_approve')
@Component({
selector: 'ds-claimed-task-actions-approve',
styleUrls: ['./claimed-task-actions-approve.component.scss'],
templateUrl: './claimed-task-actions-approve.component.html',
})
/**
* Component for displaying and processing the approve action on a workflow task item
*/
export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstractComponent {
export class ClaimedTaskActionsApproveComponent {
constructor(protected claimedTaskService: ClaimedTaskDataService) {
super();
}
/**
* A boolean representing if a reject operation is pending
* Approve the task
*/
@Input() processingApprove: boolean;
/**
* CSS classes to append to reject button
*/
@Input() wrapperClass: string;
/**
* An event fired when a approve action is confirmed.
*/
@Output() approve: EventEmitter<any> = new EventEmitter<any>();
/**
* Emit approve event
*/
confirmApprove() {
this.approve.emit();
process() {
this.processing$.next(true);
this.claimedTaskService.approveTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
}
}

View File

@@ -1,20 +1,13 @@
<a [class.disabled]="!(object.workflowitem | async)?.hasSucceeded"
class="btn btn-primary mt-1 mb-3"
ngbTooltip="{{'submission.workflow.tasks.claimed.edit_help' | translate}}"
[routerLink]="['/workflowitems/' + (object?.workflowitem | async)?.payload?.id + '/edit']"
role="button">
<i class="fa fa-edit"></i> {{'submission.workflow.tasks.claimed.edit' | translate}}
</a>
<ds-claimed-task-actions-approve [processingApprove]="(processingApprove$ | async)"
[wrapperClass]="'mt-1 mb-3'"
(approve)="approve()"></ds-claimed-task-actions-approve>
<ds-claimed-task-actions-reject [processingReject]="(processingReject$ | async)"
[wrapperClass]="'mt-1 mb-3'"
(reject)="reject($event)"></ds-claimed-task-actions-reject>
<ds-claimed-task-actions-return-to-pool [processingReturnToPool]="(processingReturnToPool$ | async)"
[wrapperClass]="'mt-1 mb-3'"
(returnToPool)="returnToPool()"></ds-claimed-task-actions-return-to-pool>
<ng-container *ngVar="(actionRD$ | async)?.payload as workflowAction">
<div class="mt-1 mb-3">
<ds-claimed-task-actions-loader *ngFor="let option of workflowAction?.options"
[option]="option"
[object]="object"
(processCompleted)="handleActionResponse($event)">
</ds-claimed-task-actions-loader>
<ds-claimed-task-actions-loader [option]="returnToPoolOption"
[object]="object"
(processCompleted)="handleActionResponse($event)">
</ds-claimed-task-actions-loader>
</div>
</ng-container>

View File

@@ -1,13 +1,12 @@
import { Component, Injector, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.model';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { isNotUndefined } from '../../empty.util';
import { WorkflowItem } from '../../../core/submission/models/workflowitem.model';
import { RemoteData } from '../../../core/data/remote-data';
@@ -15,6 +14,9 @@ import { MyDSpaceActionsComponent } from '../mydspace-actions';
import { NotificationsService } from '../../notifications/notifications.service';
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';
/**
* This component represents actions related to ClaimedTask object.
@@ -37,19 +39,15 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
public workflowitem$: Observable<WorkflowItem>;
/**
* A boolean representing if an approve operation is pending
* The workflow action available for this task
*/
public processingApprove$ = new BehaviorSubject<boolean>(false);
public actionRD$: Observable<RemoteData<WorkflowAction>>;
/**
* A boolean representing if a reject operation is pending
* The option used to render the "return to pool" component
* Every claimed task contains this option
*/
public processingReject$ = new BehaviorSubject<boolean>(false);
/**
* A boolean representing if a return to pool operation is pending
*/
public processingReturnToPool$ = new BehaviorSubject<boolean>(false);
public returnToPoolOption = WORKFLOW_TASK_OPTION_RETURN;
/**
* Initialize instance variables
@@ -60,13 +58,15 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
* @param {TranslateService} translate
* @param {SearchService} searchService
* @param {RequestService} requestService
* @param workflowActionService
*/
constructor(protected injector: Injector,
protected router: Router,
protected notificationsService: NotificationsService,
protected translate: TranslateService,
protected searchService: SearchService,
protected requestService: RequestService) {
protected requestService: RequestService,
protected workflowActionService: WorkflowActionDataService) {
super(ClaimedTask.type, injector, router, notificationsService, translate, searchService, requestService);
}
@@ -75,6 +75,7 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
*/
ngOnInit() {
this.initObjects(this.object);
this.initAction(this.object);
}
/**
@@ -90,39 +91,12 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
}
/**
* Approve the task.
* Init the WorkflowAction
*
* @param object
*/
approve() {
this.processingApprove$.next(true);
this.objectDataService.approveTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processingApprove$.next(false);
this.handleActionResponse(res.hasSucceeded);
});
}
/**
* Reject the task.
*/
reject(reason) {
this.processingReject$.next(true);
this.objectDataService.rejectTask(reason, this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processingReject$.next(false);
this.handleActionResponse(res.hasSucceeded);
});
}
/**
* Return task to the pool.
*/
returnToPool() {
this.processingReturnToPool$.next(true);
this.objectDataService.returnToPoolTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processingReturnToPool$.next(false);
this.handleActionResponse(res.hasSucceeded);
});
initAction(object: ClaimedTask) {
this.actionRD$ = this.workflowActionService.findById(object.action);
}
}

View File

@@ -0,0 +1,7 @@
<a *ngIf="object"
class="btn btn-primary"
ngbTooltip="{{'submission.workflow.tasks.claimed.edit_help' | translate}}"
[routerLink]="['/workflowitems/' + (object.workflowitem | async)?.payload.id + '/edit']"
role="button">
<i class="fa fa-edit"></i> {{'submission.workflow.tasks.claimed.edit' | translate}}
</a>

View File

@@ -0,0 +1,65 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskActionsEditMetadataComponent } from './claimed-task-actions-approve.component';
import { MockTranslateLoader } from '../../../mocks/mock-translate-loader';
let component: ClaimedTaskActionsEditMetadataComponent;
let fixture: ComponentFixture<ClaimedTaskActionsEditMetadataComponent>;
describe('ClaimedTaskActionsApproveComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: MockTranslateLoader
}
})
],
declarations: [ClaimedTaskActionsEditMetadataComponent],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedTaskActionsEditMetadataComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClaimedTaskActionsEditMetadataComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
fixture = null;
component = null;
});
it('should display approve button', () => {
const btn = fixture.debugElement.query(By.css('.btn-success'));
expect(btn).toBeDefined();
});
it('should display spin icon when approve is pending', () => {
component.processingApprove = true;
fixture.detectChanges();
const span = fixture.debugElement.query(By.css('.btn-success .fa-spin'));
expect(span).toBeDefined();
});
it('should emit approve event', () => {
spyOn(component.approve, 'emit');
component.confirmApprove();
fixture.detectChanges();
expect(component.approve.emit).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,20 @@
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 { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
@rendersWorkflowTaskOption('submit_edit_metadata')
@Component({
selector: 'ds-claimed-task-actions-edit-metadata',
styleUrls: ['./claimed-task-actions-edit-metadata.component.scss'],
templateUrl: './claimed-task-actions-edit-metadata.component.html',
})
/**
* 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
}
}

View File

@@ -1,10 +1,10 @@
<ng-template #rejectTipContent><p [innerHTML]="'submission.workflow.tasks.claimed.reject_help' | translate"></p></ng-template>
<button [className]="'btn btn-danger ' + wrapperClass"
[ngbTooltip]="rejectTipContent"
[disabled]="processingReject"
[disabled]="processing$ | async"
(click)="openRejectModal(rejectModal)" >
<span *ngIf="processingReject"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!processingReject"><i class="fa fa-trash"></i> {{'submission.workflow.tasks.claimed.reject.submit' | translate}}</span>
<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-trash"></i> {{'submission.workflow.tasks.claimed.reject.submit' | translate}}</span>
</button>
<ng-template #rejectModal let-c="close" let-d="dismiss">
@@ -21,17 +21,17 @@
<div class="alert alert-info" role="alert">
{{'submission.workflow.tasks.claimed.reject.reason.info' | translate}}
</div>
<form (ngSubmit)="confirmReject(rejectModal);" [formGroup]="rejectForm" >
<form (ngSubmit)="process(rejectModal);" [formGroup]="rejectForm" >
<textarea style="width: 100%"
formControlName="reason"
rows="4"
placeholder="{{'submission.workflow.tasks.claimed.reject.reason.placeholder' | translate}}"></textarea>
<button id="btn-chat"
class="btn btn-danger btn-lg btn-block mt-3"
[disabled]="!rejectForm.valid || processingReject"
[disabled]="!rejectForm.valid || (processing$ | async)"
type="submit">
<span *ngIf="processingReject"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!processingReject">{{'submission.workflow.tasks.claimed.reject.reason.submit' | translate}}</span>
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="!(processing$ | async)">{{'submission.workflow.tasks.claimed.reject.reason.submit' | translate}}</span>
</button>
</form>
</div>

View File

@@ -1,26 +1,22 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, OnInit, Output } 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';
@rendersWorkflowTaskOption('submit_reject')
@Component({
selector: 'ds-claimed-task-actions-reject',
styleUrls: ['./claimed-task-actions-reject.component.scss'],
templateUrl: './claimed-task-actions-reject.component.html',
})
export class ClaimedTaskActionsRejectComponent implements OnInit {
/**
* A boolean representing if a reject operation is pending
*/
@Input() processingReject: boolean;
/**
* CSS classes to append to reject button
*/
@Input() wrapperClass: string;
/**
* Component for displaying and processing the reject action on a workflow task item
*/
export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstractComponent implements OnInit {
/**
* An event fired when a reject action is confirmed.
* Event's payload equals to reject reason.
@@ -42,8 +38,12 @@ export class ClaimedTaskActionsRejectComponent implements OnInit {
*
* @param {FormBuilder} formBuilder
* @param {NgbModal} modalService
* @param claimedTaskService
*/
constructor(private formBuilder: FormBuilder, private modalService: NgbModal) {
constructor(protected claimedTaskService: ClaimedTaskDataService,
private formBuilder: FormBuilder,
private modalService: NgbModal) {
super();
}
/**
@@ -53,17 +53,20 @@ export class ClaimedTaskActionsRejectComponent implements OnInit {
this.rejectForm = this.formBuilder.group({
reason: ['', Validators.required]
});
}
/**
* Close modal and emit reject event
* Reject the task
*/
confirmReject() {
this.processingReject = true;
this.modalRef.close('Send Button');
process() {
this.processing$.next(true);
const reason = this.rejectForm.get('reason').value;
this.reject.emit(reason);
this.modalRef.close('Send Button');
this.claimedTaskService.rejectTask(reason, this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
}
/**

View File

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

@@ -1,32 +1,35 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
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 { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
export const WORKFLOW_TASK_OPTION_RETURN = 'return_to_pool';
@rendersWorkflowTaskOption(WORKFLOW_TASK_OPTION_RETURN)
@Component({
selector: 'ds-claimed-task-actions-return-to-pool',
styleUrls: ['./claimed-task-actions-return-to-pool.component.scss'],
templateUrl: './claimed-task-actions-return-to-pool.component.html',
})
/**
* Component for displaying and processing the return to pool action on a workflow task item
*/
export class ClaimedTaskActionsReturnToPoolComponent extends ClaimedTaskActionsAbstractComponent {
export class ClaimedTaskActionsReturnToPoolComponent {
constructor(protected claimedTaskService: ClaimedTaskDataService) {
super();
}
/**
* A boolean representing if a return to pool operation is pending
* Return task to pool
*/
@Input() processingReturnToPool: boolean;
/**
* CSS classes to append to return to pool button
*/
@Input() wrapperClass: string;
/**
* An event fired when a return to pool action is confirmed.
*/
@Output() returnToPool: EventEmitter<any> = new EventEmitter<any>();
/**
* Emit returnToPool event
*/
confirmReturnToPool() {
this.returnToPool.emit();
process() {
this.processing$.next(true);
this.claimedTaskService.returnToPoolTask(this.object.id)
.subscribe((res: ProcessTaskResponse) => {
this.processing$.next(false);
this.processCompleted.emit(res.hasSucceeded);
});
}
}

View File

@@ -0,0 +1,23 @@
import { hasNoValue } from '../../../empty.util';
const map = new Map();
/**
* Decorator used for rendering ClaimedTaskActions pages by option type
*/
export function rendersWorkflowTaskOption(option: string) {
return function decorator(component: any) {
if (hasNoValue(map.get(option))) {
map.set(option, component);
} else {
throw new Error(`There can't be more than one component to render ClaimedTaskActions for option "${option}"`);
}
};
}
/**
* Get the component used for rendering a ClaimedTaskActions page by option type
*/
export function getComponentByWorkflowTaskOption(option: string) {
return map.get(option);
}

View File

@@ -0,0 +1 @@
<ng-template dsClaimedTaskActions></ng-template>

View File

@@ -0,0 +1,85 @@
import {
Component,
ComponentFactoryResolver,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild
} from '@angular/core';
import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { hasValue } from '../../../empty.util';
import { Subscription } from 'rxjs/internal/Subscription';
@Component({
selector: 'ds-claimed-task-actions-loader',
templateUrl: './claimed-task-actions-loader.component.html'
})
/**
* Component for loading a ClaimedTaskAction component depending on the "option" input
* Passes on the ClaimedTask to the component and subscribes to the processCompleted output
*/
export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy {
/**
* The ClaimedTask object
*/
@Input() object: ClaimedTask;
/**
* The name of the option to render
* Passed on to the decorator to fetch the relevant component for this option
*/
@Input() option: string;
/**
* Emits the success or failure of a processed action
*/
@Output() processCompleted: EventEmitter<boolean> = new EventEmitter<boolean>();
/**
* Directive to determine where the dynamic child component is located
*/
@ViewChild(ClaimedTaskActionsDirective, {static: true}) claimedTaskActionsDirective: ClaimedTaskActionsDirective;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = [];
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
/**
* Fetch, create and initialize the relevant component
*/
ngOnInit(): void {
const comp = getComponentByWorkflowTaskOption(this.option);
if (hasValue(comp)) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp);
const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent);
componentInstance.object = this.object;
if (hasValue(componentInstance.processCompleted)) {
this.subs.push(componentInstance.processCompleted.subscribe((success) => this.processCompleted.emit(success)));
}
}
}
/**
* Unsubscribe from open subscriptions
*/
ngOnDestroy(): void {
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe());
}
}

View File

@@ -0,0 +1,11 @@
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[dsClaimedTaskActions]',
})
/**
* Directive used as a hook to know where to inject the dynamic Claimed Task Actions component
*/
export class ClaimedTaskActionsDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@@ -177,6 +177,9 @@ import { ImportableListItemControlComponent } from './object-collection/shared/i
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
import { SortablejsModule } from 'ngx-sortablejs';
import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component';
import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive';
import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -279,6 +282,8 @@ const COMPONENTS = [
ClaimedTaskActionsApproveComponent,
ClaimedTaskActionsRejectComponent,
ClaimedTaskActionsReturnToPoolComponent,
ClaimedTaskActionsEditMetadataComponent,
ClaimedTaskActionsLoaderComponent,
ItemActionsComponent,
PoolTaskActionsComponent,
WorkflowitemActionsComponent,
@@ -402,7 +407,11 @@ const ENTRY_COMPONENTS = [
DsDynamicLookupRelationSearchTabComponent,
DsDynamicLookupRelationSelectionTabComponent,
DsDynamicLookupRelationExternalSourceTabComponent,
ExternalSourceEntryImportModalComponent
ExternalSourceEntryImportModalComponent,
ClaimedTaskActionsApproveComponent,
ClaimedTaskActionsRejectComponent,
ClaimedTaskActionsReturnToPoolComponent,
ClaimedTaskActionsEditMetadataComponent
];
const SHARED_ITEM_PAGE_COMPONENTS = [
@@ -430,7 +439,8 @@ const DIRECTIVES = [
AutoFocusDirective,
RoleDirective,
MetadataRepresentationDirective,
ListableObjectDirective
ListableObjectDirective,
ClaimedTaskActionsDirective
];
@NgModule({