[ADF-4349] Cloud - task-form-component - Create a new component (#4620)

* [ADF-4349] Created task form cloud

* [ADF-4349] Used task-form in demo shell

* [ADF-4349] Used task directives

* [ADF-4349] Added tests

* [ADF-4349] Added documentation

* [ADF-4349] Added translation for buttons
This commit is contained in:
Deepak Paul 2019-04-17 20:34:59 +05:30 committed by Maurizio Vitale
parent bcdfcee397
commit 21fd0299bd
17 changed files with 827 additions and 54 deletions

View File

@ -3,23 +3,15 @@
<div fxLayout="column" fxFill fxLayoutGap="2px">
<div fxLayout="row" fxFill>
<div fxLayout="column" fxFlex="80%">
<div class="adf-task-control">
<button mat-button (click)="goBack()">Cancel</button>
<button mat-button color="primary" *ngIf="canCompleteTask()" adf-cloud-complete-task
(success)="onCompletedTask()">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }}</button>
<button mat-button color="primary" *ngIf="canClaimTask()" adf-cloud-claim-task
(success)="onClaimTask()">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}</button>
<button mat-button color="primary" *ngIf="canUnClaimTask()" adf-cloud-unclaim-task
(success)="onUnclaimTask()">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}</button>
</div>
<adf-cloud-form *ngIf="hasTaskForm()" fxFlex="100%"
<adf-task-form-cloud
[appName]="appName"
[taskId]="taskId"
(formCompleted)="onTaskCompleted()"
(cancelClick)="goBack()"
(taskClaimed)="onClaimTask()"
(taskCompleted)="onTaskCompleted()"
(taskUnclaimed)="onUnclaimTask()"
(formSaved)="onFormSaved()">
</adf-cloud-form>
</adf-task-form-cloud>
</div>
<adf-cloud-task-header fxFlex
[appName]="appName"

View File

@ -15,18 +15,17 @@
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TaskDetailsCloudModel, TaskCloudService, UploadCloudWidgetComponent } from '@alfresco/adf-process-services-cloud';
import { UploadCloudWidgetComponent } from '@alfresco/adf-process-services-cloud';
import { NotificationService, FormRenderingService } from '@alfresco/adf-core';
@Component({
templateUrl: './task-details-cloud-demo.component.html',
styleUrls: ['./task-details-cloud-demo.component.scss']
})
export class TaskDetailsCloudDemoComponent implements OnInit {
export class TaskDetailsCloudDemoComponent {
taskDetails: TaskDetailsCloudModel;
taskId: string;
appName: string;
@ -34,7 +33,6 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private formRenderingService: FormRenderingService,
private taskCloudService: TaskCloudService,
private notificationService: NotificationService
) {
this.route.params.subscribe((params) => {
@ -47,37 +45,10 @@ export class TaskDetailsCloudDemoComponent implements OnInit {
}
ngOnInit() {
this.loadTaskDetailsById(this.appName, this.taskId);
}
loadTaskDetailsById(appName: string, taskId: string) {
this.taskCloudService.getTaskById(appName, taskId).subscribe(
(taskDetails: TaskDetailsCloudModel ) => {
this.taskDetails = taskDetails;
});
}
isTaskValid(): boolean {
return this.appName !== undefined && this.taskId !== undefined;
}
canCompleteTask(): boolean {
return this.taskDetails && !this.taskDetails.formKey && this.taskCloudService.canCompleteTask(this.taskDetails);
}
canClaimTask(): boolean {
return this.taskDetails && this.taskCloudService.canClaimTask(this.taskDetails);
}
canUnClaimTask(): boolean {
return this.taskDetails && this.taskCloudService.canUnclaimTask(this.taskDetails);
}
hasTaskForm(): boolean {
return this.taskDetails && this.taskDetails.formKey;
}
goBack() {
this.router.navigate([`/cloud/${this.appName}/`]);
}

View File

@ -0,0 +1,63 @@
---
Title: Form cloud component
Added: v3.2.0
Status: Active
Last reviewed: 2019-04-17
---
# [Task form cloud component](../../../lib/process-services-cloud/src/lib/form/components/task-form-cloud.component.ts "Defined in task-form-cloud.component.ts")
Shows a [`form`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts) for a task.
## Contents
- [Basic Usage](#basic-usage)
- [Class members](#class-members)
- [Properties](#properties)
- [Events](#events)
- [See also](#see-also)
## Basic Usage
```html
<adf-task-form-cloud
[appName]="appName"
[taskId]="taskId">
</adf-task-form-cloud>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| appName | `string` | | App id to fetch corresponding form and values. |
| taskId | `string` | | Task id to fetch corresponding form and values. |
| showRefreshButton | `boolean` | false | Toggle rendering of the `Refresh` button. |
| showValidationIcon | `boolean` | true | Toggle rendering of the `Validation` icon. |
| showCancelButton | `boolean` | true | Toggle rendering of the `Cancel` outcome button. |
| showCompleteButton | `boolean` | true | Toggle rendering of the `Complete` outcome button. |
| showSaveButton | `boolean` | true | Toggle rendering of the `Save` outcome button. |
| readOnly | `boolean` | false | Toggle readonly state of the task. |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloud`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when the form is saved. |
| formCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormCloud`](../../../lib/process-services-cloud/src/lib/form/models/form-cloud.model.ts)`>` | Emitted when the form is submitted with the `Complete` outcome. |
| taskCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`string`>` | Emitted when the task is completed. |
| taskClaimed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`string`>` | Emitted when the task is claimed. |
| taskUnclaimed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`string`>` | Emitted when the task is unclaimed. |
| cancelClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`string`>` | Emitted when the cancel button is clicked. |
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when any error occurs. |
## See also
- [Form component](./form-cloud.component.md)
- [Form field model](../../core/models/form-field.model.md)
- [Form cloud service](../services/form-cloud.service.md)

View File

@ -1,6 +1,9 @@
{
"SAVE": "SAVE",
"COMPLETE": "COMPLETE",
"CANCEL": "CANCEL",
"CLAIM": "CLAIM",
"UNCLAIM": "UNCLAIM",
"START PROCESS": "START PROCESS",
"FORM": {
"START_FORM": {

View File

@ -750,4 +750,52 @@ describe('FormCloudComponent', () => {
radioFieldById = formFields.find((field) => field.id === 'radiobuttons1');
expect(radioFieldById.value).toBe('option_2');
});
it('should emit executeOutcome on [claim] outcome click', (done) => {
const formModel = new FormCloud();
const outcome = new FormOutcomeModel(<any> formModel, {
id: FormCloud.CLAIM_OUTCOME,
name: 'CLAIM',
isSystem: true
});
formComponent.form = formModel;
formComponent.executeOutcome.subscribe(() => {
done();
});
formComponent.onOutcomeClicked(outcome);
});
it('should emit executeOutcome on [unclaim] outcome click', (done) => {
const formModel = new FormCloud();
const outcome = new FormOutcomeModel(<any> formModel, {
id: FormCloud.UNCLAIM_OUTCOME,
name: 'UNCLAIM',
isSystem: true
});
formComponent.form = formModel;
formComponent.executeOutcome.subscribe(() => {
done();
});
formComponent.onOutcomeClicked(outcome);
});
it('should emit executeOutcome on [cancel] outcome click', (done) => {
const formModel = new FormCloud();
const outcome = new FormOutcomeModel(<any> formModel, {
id: FormCloud.CANCEL_OUTCOME,
name: 'CANCEL',
isSystem: true
});
formComponent.form = formModel;
formComponent.executeOutcome.subscribe(() => {
done();
});
formComponent.onOutcomeClicked(outcome);
});
});

View File

@ -53,6 +53,18 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges {
@Input()
data: TaskVariableCloud[];
/** Toggle rendering of the `Cancel` outcome button. */
@Input()
showCancelButton = false;
/** Toggle rendering of the `Claim` outcome button. */
@Input()
showClaimButton = false;
/** Toggle rendering of the `Unclaim` outcome button. */
@Input()
showUnclaimButton = false;
/** Emitted when the form is submitted with the `Save` or custom outcomes. */
@Output()
formSaved: EventEmitter<FormCloud> = new EventEmitter<FormCloud>();
@ -147,6 +159,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges {
(data) => {
this.data = data[1];
const parsedForm = this.parseForm(data[0]);
this.appendCustomOutcomes(parsedForm);
this.visibilityService.refreshVisibility(<any> parsedForm);
parsedForm.validateForm();
this.form = parsedForm;
@ -178,6 +191,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges {
.subscribe(
(form) => {
const parsedForm = this.parseForm(form);
this.appendCustomOutcomes(parsedForm);
this.visibilityService.refreshVisibility(<any> parsedForm);
parsedForm.validateForm();
this.form = parsedForm;
@ -293,4 +307,39 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges {
protected storeFormAsMetadata() {
}
private appendCustomOutcomes(form: FormCloud): FormCloud {
if (this.showClaimButton) {
const claimOutcome = new FormOutcomeModel(<any> form, {
id: FormCloud.CLAIM_OUTCOME,
name: 'CLAIM',
isSystem: true
});
form.outcomes.unshift(claimOutcome);
}
if (this.showUnclaimButton) {
const unclaimOutcome = new FormOutcomeModel(<any> form, {
id: FormCloud.UNCLAIM_OUTCOME,
name: 'UNCLAIM',
isSystem: true
});
form.outcomes.unshift(unclaimOutcome);
}
if (this.showCancelButton) {
const cancelOutcome = new FormOutcomeModel(<any> form, {
id: FormCloud.CANCEL_OUTCOME,
name: 'CANCEL',
isSystem: true
});
form.outcomes.unshift(cancelOutcome);
}
return form;
}
}

View File

@ -0,0 +1,56 @@
<div *ngIf="taskDetails">
<adf-cloud-form *ngIf="hasForm(); else withoutForm"
[appName]="appName"
[taskId]="taskId"
[readOnly]="isReadOnly()"
[showRefreshButton]="showRefreshButton"
[showValidationIcon]="showValidationIcon"
[showCompleteButton]="canCompleteTask()"
[showSaveButton]="canCompleteTask()"
[showCancelButton]="showCancelButton"
[showClaimButton]="canClaimTask()"
[showUnclaimButton]="canUnclaimTask()"
(executeOutcome)="onExecuteOutcome($event.outcome)"
(formSaved)="onFormSaved($event)"
(formCompleted)="onFormCompleted($event)"
(formError)="onError($event)">
</adf-cloud-form>
<ng-template #withoutForm>
<mat-card class="adf-task-form-container">
<mat-card-header>
<mat-card-title>
<h4>
<span class="adf-form-title">
{{taskDetails.name}}
<ng-container *ngIf="!taskDetails.name">
{{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}}
</ng-container>
</span>
</h4>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<adf-empty-content
[icon]="'description'"
[title]="'ADF_CLOUD_TASK_FORM.EMPTY_FORM.TITLE'"
[subtitle]="'ADF_CLOUD_TASK_FORM.EMPTY_FORM.SUBTITLE'">
</adf-empty-content>
</mat-card-content>
<mat-card-actions class="adf-task-form-actions">
<button mat-button *ngIf="showCancelButton" id="adf-cloud-cancel-task" (click)="onCancelClick()">
{{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
</button>
<button mat-button *ngIf="canClaimTask()" adf-cloud-claim-task [appName]="appName" [taskId]="taskId" (success)="onClaimTask()">
{{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CLAIM' | translate}}
</button>
<button mat-button *ngIf="canUnclaimTask()" adf-cloud-unclaim-task [appName]="appName" [taskId]="taskId" (success)="onUnclaimTask()">
{{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.UNCLAIM' | translate}}
</button>
<button mat-button *ngIf="canCompleteTask()" adf-cloud-complete-task [appName]="appName" [taskId]="taskId" (success)="onCompleteTask()" color="primary">
{{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE' | translate}}
</button>
</mat-card-actions>
</mat-card>
</ng-template>
</div>

View File

@ -0,0 +1,32 @@
@mixin adf-task-form-cloud-theme($theme) {
$config: mat-typography-config();
.adf-task-form {
&-container {
overflow: hidden;
}
&-actions {
float: right;
padding-bottom: 25px !important;
padding-right: 25px !important;
& .mat-button {
height: 36px;
border-radius: 5px;
}
& .mat-button-wrapper {
width: 58px;
height: 20px;
opacity: 0.54;
font-size: mat-font-size($config, body-2);
font-weight: bold;
}
}
}
}

View File

@ -0,0 +1,345 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudModule } from '../form-cloud.module';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { TaskFormCloudComponent } from './task-form-cloud.component';
import { setupTestBed, IdentityUserService } from '@alfresco/adf-core';
import { TaskCloudService, TaskDetailsCloudModel } from '../../task/public-api';
import { of } from 'rxjs';
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
const taskDetails = {
appName: 'simple-app',
assignee: 'admin.adf',
completedDate: null,
createdDate: 1555419255340,
description: null,
formKey: null,
id: 'bd6b1741-6046-11e9-80f0-0a586460040d',
name: 'Task1',
owner: 'admin.adf',
standAlone: true,
status: 'ASSIGNED'
};
describe('TaskFormCloudComponent', () => {
let taskCloudService: TaskCloudService;
let identityUserService: IdentityUserService;
let getTaskSpy: jasmine.Spy;
let getCurrentUserSpy: jasmine.Spy;
let debugElement: DebugElement;
let component: TaskFormCloudComponent;
let fixture: ComponentFixture<TaskFormCloudComponent>;
setupTestBed({
imports: [ProcessServiceCloudTestingModule, FormCloudModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
taskDetails.status = 'ASSIGNED';
identityUserService = TestBed.get(IdentityUserService);
getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({username: 'admin.adf'});
taskCloudService = TestBed.get(TaskCloudService);
getTaskSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
fixture = TestBed.createComponent(TaskFormCloudComponent);
debugElement = fixture.debugElement;
component = fixture.componentInstance;
});
it('should create TaskFormCloudComponent ', () => {
expect(component instanceof TaskFormCloudComponent).toBe(true);
});
describe('Complete button', () => {
it('should show complete button when status is ASSIGNED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
expect(completeBtn.nativeElement).toBeDefined();
});
}));
it('should not show complete button when status is ASSIGNED but assigned to a different person', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
getCurrentUserSpy.and.returnValue({});
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
expect(completeBtn).toBeNull();
});
}));
it('should not show complete button when showCompleteButton=false', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.showCompleteButton = false;
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
expect(completeBtn).toBeNull();
});
}));
});
describe('Claim/Unclaim buttons', () => {
it('should show unclaim button when status is ASSIGNED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn.nativeElement).toBeDefined();
});
}));
it('should not show unclaim button when status is ASSIGNED but assigned to different person', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
getCurrentUserSpy.and.returnValue({});
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
}));
it('should not show unclaim button when status is not ASSIGNED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = '';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
}));
it('should show claim button when status is CREATED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = 'CREATED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
expect(claimBtn.nativeElement).toBeDefined();
});
}));
it('should not show claim button when status is not CREATED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = '';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
expect(claimBtn).toBeNull();
});
}));
});
describe('Cancel button', () => {
it('should show cancel button by default', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
expect(cancelBtn.nativeElement).toBeDefined();
});
}));
it('should not show cancel button when showCancelButton=false', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.showCancelButton = false;
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
expect(cancelBtn).toBeNull();
});
}));
});
describe('Inputs', () => {
it('should not show complete/claim/unclaim buttons when readOnly=true', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
component.readOnly = true;
component.loadTask();
fixture.detectChanges();
fixture.whenStable().then(() => {
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
expect(completeBtn).toBeNull();
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
expect(claimBtn).toBeNull();
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
expect(cancelBtn.nativeElement).toBeDefined();
});
}));
it('should load data when appName changes', () => {
component.taskId = 'task1';
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
expect(getTaskSpy).toHaveBeenCalled();
});
it('should load data when taskId changes', () => {
component.appName = 'app1';
component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) });
expect(getTaskSpy).toHaveBeenCalled();
});
it('should not load data when appName changes and taskId is not defined', () => {
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
expect(getTaskSpy).not.toHaveBeenCalled();
});
it('should not load data when taskId changes and appName is not defined', () => {
component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) });
expect(getTaskSpy).not.toHaveBeenCalled();
});
});
describe('Events', () => {
it('should emit cancelClick when cancel button is clicked', (done) => {
component.appName = 'app1';
component.taskId = 'task1';
component.cancelClick.subscribe(() => {
done();
});
component.loadTask();
fixture.detectChanges();
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
cancelBtn.nativeElement.click();
});
it('should emit taskCompleted when task is completed', (done) => {
spyOn(taskCloudService, 'completeTask').and.returnValue(of({}));
component.appName = 'app1';
component.taskId = 'task1';
component.taskCompleted.subscribe(() => {
done();
});
component.loadTask();
fixture.detectChanges();
const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
completeBtn.nativeElement.click();
});
it('should emit taskClaimed when task is claimed', (done) => {
spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
taskDetails.status = 'CREATED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
component.appName = 'app1';
component.taskId = 'task1';
component.taskClaimed.subscribe(() => {
done();
});
component.loadTask();
fixture.detectChanges();
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
claimBtn.nativeElement.click();
});
it('should emit taskUnclaimed when task is unclaimed', (done) => {
spyOn(taskCloudService, 'unclaimTask').and.returnValue(of({}));
taskDetails.status = 'ASSIGNED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
component.appName = 'app1';
component.taskId = 'task1';
component.taskUnclaimed.subscribe(() => {
done();
});
component.loadTask();
fixture.detectChanges();
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
unclaimBtn.nativeElement.click();
});
it('should emit error when error occurs', (done) => {
component.appName = 'app1';
component.taskId = 'task1';
component.error.subscribe(() => {
done();
});
component.onError({});
});
});
});

View File

@ -0,0 +1,191 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Component, EventEmitter, Input, OnChanges,
Output, SimpleChanges
} from '@angular/core';
import { FormCloud } from '../models/form-cloud.model';
import { TaskDetailsCloudModel, TaskCloudService } from '../../task/public-api';
import { IdentityUserService, FormOutcomeModel } from '@alfresco/adf-core';
@Component({
selector: 'adf-task-form-cloud',
templateUrl: './task-form-cloud.component.html',
styleUrls: ['./task-form-cloud.component.scss']
})
export class TaskFormCloudComponent implements OnChanges {
/** App id to fetch corresponding form and values. */
@Input()
appName: string;
/** Task id to fetch corresponding form and values. */
@Input()
taskId: string;
/** Toggle rendering of the `Refresh` button. */
@Input()
showRefreshButton = false;
/** Toggle rendering of the `Validation` icon. */
@Input()
showValidationIcon = true;
/** Toggle rendering of the `Cancel` button. */
@Input()
showCancelButton = true;
/** Toggle rendering of the `Complete` button. */
@Input()
showCompleteButton = true;
/** Toggle readonly state of the task. */
@Input()
readOnly = false;
/** Emitted when the form is saved. */
@Output()
formSaved: EventEmitter<FormCloud> = new EventEmitter<FormCloud>();
/** Emitted when the form is submitted with the `Complete` outcome. */
@Output()
formCompleted: EventEmitter<FormCloud> = new EventEmitter<FormCloud>();
/** Emitted when the task is completed. */
@Output()
taskCompleted: EventEmitter<string> = new EventEmitter<string>();
/** Emitted when the task is claimed. */
@Output()
taskClaimed: EventEmitter<string> = new EventEmitter<string>();
/** Emitted when the task is unclaimed. */
@Output()
taskUnclaimed: EventEmitter<string> = new EventEmitter<string>();
/** Emitted when the cancel button is clicked. */
@Output()
cancelClick: EventEmitter<string> = new EventEmitter<string>();
/** Emitted when any error occurs. */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
taskDetails: TaskDetailsCloudModel;
constructor(
private taskCloudService: TaskCloudService,
private identityUserService: IdentityUserService) {
}
ngOnChanges(changes: SimpleChanges) {
const appName = changes['appName'];
if (appName && appName.currentValue && this.taskId) {
this.loadTask();
return;
}
const taskId = changes['taskId'];
if (taskId && taskId.currentValue && this.appName) {
this.loadTask();
return;
}
}
loadTask() {
this.taskCloudService.getTaskById(this.appName, this.taskId).subscribe((details: TaskDetailsCloudModel) => {
this.taskDetails = details;
});
}
hasForm(): boolean {
return this.taskDetails && !!this.taskDetails.formKey;
}
canCompleteTask(): boolean {
return this.showCompleteButton && !this.readOnly && this.taskCloudService.canCompleteTask(this.taskDetails);
}
canClaimTask(): boolean {
return !this.readOnly && this.taskCloudService.canClaimTask(this.taskDetails);
}
canUnclaimTask(): boolean {
return !this.readOnly && this.taskCloudService.canUnclaimTask(this.taskDetails);
}
isReadOnly(): boolean {
return this.readOnly || this.taskDetails.isCompleted();
}
onCompleteTask() {
this.taskCompleted.emit(this.taskId);
}
onClaimTask() {
this.taskClaimed.emit(this.taskId);
}
onUnclaimTask() {
this.taskUnclaimed.emit(this.taskId);
}
onCancelClick() {
this.cancelClick.emit(this.taskId);
}
claimTask() {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
this.taskCloudService.claimTask(this.appName, this.taskId, currentUser).subscribe(
() => {
this.taskClaimed.emit(this.taskId);
});
}
unclaimTask() {
this.taskCloudService.unclaimTask(this.appName, this.taskId).subscribe(
() => {
this.taskUnclaimed.emit(this.taskId);
});
}
onExecuteOutcome(outcome: FormOutcomeModel) {
if (outcome.id === FormCloud.CANCEL_OUTCOME) {
this.onCancelClick();
} else if (outcome.id === FormCloud.CLAIM_OUTCOME) {
this.claimTask();
} else if (outcome.id === FormCloud.UNCLAIM_OUTCOME) {
this.unclaimTask();
}
}
onFormSaved(form: FormCloud) {
this.formSaved.emit(form);
}
onFormCompleted(form: FormCloud) {
this.formCompleted.emit(form);
this.taskCompleted.emit(this.taskId);
}
onError(data: any) {
this.error.emit(data);
}
}

View File

@ -23,6 +23,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormCloudComponent } from './components/form-cloud.component';
import { UploadCloudWidgetComponent } from './components/upload-cloud.widget';
import { MaterialModule } from '../material.module';
import { TaskFormCloudComponent } from './components/task-form-cloud.component';
import { TaskModule } from '../task/task.module';
@NgModule({
imports: [
@ -34,14 +36,15 @@ import { MaterialModule } from '../material.module';
FormsModule,
ReactiveFormsModule,
FormBaseModule,
CoreModule
CoreModule,
TaskModule
],
declarations: [FormCloudComponent, UploadCloudWidgetComponent],
declarations: [FormCloudComponent, UploadCloudWidgetComponent, TaskFormCloudComponent],
entryComponents: [
UploadCloudWidgetComponent
],
exports: [
FormCloudComponent, UploadCloudWidgetComponent
FormCloudComponent, UploadCloudWidgetComponent, TaskFormCloudComponent
]
})
export class FormCloudModule { }

View File

@ -28,6 +28,10 @@ export class FormCloud {
static COMPLETE_OUTCOME: string = '$complete';
static START_PROCESS_OUTCOME: string = '$startProcess';
static CANCEL_OUTCOME: string = '$cancel';
static CLAIM_OUTCOME: string = '$claim';
static UNCLAIM_OUTCOME: string = '$unclaim';
readonly id: string;
nodeId: string;
readonly name: string;

View File

@ -19,4 +19,5 @@ export * from './models/form-cloud.model';
export * from './models/task-variable-cloud.model';
export * from './components/form-cloud.component';
export * from './components/upload-cloud.widget';
export * from './components/task-form-cloud.component';
export * from './services/form-cloud.service';

View File

@ -228,5 +228,18 @@
"PARENT_ID": "Parent Id",
"NONE": "None"
}
},
"ADF_CLOUD_TASK_FORM": {
"EMPTY_FORM": {
"TITLE": "No form available",
"SUBTITLE": "Attach a form that can be viewed later",
"BUTTONS": {
"COMPLETE": "COMPLETE",
"CANCEL": "CANCEL",
"CLAIM": "CLAIM",
"UNCLAIM": "UNCLAIM"
}
}
}
}

View File

@ -7,6 +7,7 @@
@import './../process/process-filters/components/edit-process-filter-cloud.component.scss';
@import './../task/start-task/components/people-cloud/people-cloud.component.scss';
@import './../group/components/group-cloud.component';
@import './../form/components/task-form-cloud.component';
@mixin adf-process-services-cloud-theme($theme) {
@ -19,4 +20,5 @@
@include adf-start-task-cloud-theme($theme);
@include adf-cloud-people-theme($theme);
@include adf-cloud-group-theme($theme);
@include adf-task-form-cloud-theme($theme);
}

View File

@ -71,7 +71,7 @@ export class TaskCloudService {
*/
canCompleteTask(taskDetails: TaskDetailsCloudModel): boolean {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return taskDetails.assignee && taskDetails.assignee === currentUser && taskDetails.isAssigned();
return taskDetails && taskDetails.assignee && taskDetails.assignee === currentUser && taskDetails.isAssigned();
}
/**
@ -90,7 +90,7 @@ export class TaskCloudService {
*/
canUnclaimTask(taskDetails: TaskDetailsCloudModel): boolean {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return taskDetails.canUnclaimTask(currentUser);
return taskDetails && taskDetails.canUnclaimTask(currentUser);
}
/**

View File

@ -75,11 +75,11 @@ export class TaskDetailsCloudModel {
}
isCompleted(): boolean {
return this.status && this.status === TaskStatusEnum.COMPLETED;
return this.status === TaskStatusEnum.COMPLETED;
}
isAssigned(): boolean {
return this.status && this.status === TaskStatusEnum.ASSIGNED;
return this.status === TaskStatusEnum.ASSIGNED;
}
canClaimTask(): boolean {
@ -87,7 +87,7 @@ export class TaskDetailsCloudModel {
}
canUnclaimTask(user: string): boolean {
return this.status !== TaskStatusEnum.COMPLETED && this.assignee === user;
return this.isAssigned() && this.assignee === user;
}
}