[ADF-4432] TaskFormCloudComponent - should be read only if the task is unclaimed. (#4654)

* [ADF-4432] FormCloud - should be read only if the task is unclaimed.* Modifed formCloud component related claim/unclaim custom buttons.* Added unit tests to the recent changes* Created an component to project custom form outcomes.

* * Added documentation to the formcloudcustomoutcome component.
This commit is contained in:
siva kumar 2019-04-30 14:51:37 +05:30 committed by Eugenio Romano
parent 860529058c
commit 3291ecaccb
12 changed files with 244 additions and 134 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,34 @@
---
Title: Form cloud custom outcomes component
Added: v3.2.0
Status: Active
Last reviewed: 2019-04-12
---
# [Form cloud custom outcomes component](../../../lib/process-services-cloud/src/lib/form/components/form-cloud-custom-outcomes.component.ts "Defined in form-cloud-custom-outcomes.component.ts")
Supplies custom outcome buttons to be included in [Form cloud component](form-cloud.component.md).
![](../../docassets/images/form-cloud-custom-outcomes.component.png)
## Basic Usage
```html
<adf-cloud-form>
<adf-cloud-form-custom-outcomes>
<button mat-button (click)="onCustomOutcome1()">
Custom-outcome-1
</button>
<button mat-button (click)="onCustomOutcome2()">
Custom-outcome-2
</button>
<button mat-button (click)="onCustomOutcome3()">
Custom-outcome-3
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>
```
## See Also
- [Form cloud component](form-cloud.component.md)

View File

@ -32,6 +32,28 @@ Shows a [`form`](../../../lib/process-services-cloud/src/lib/form/models/form-cl
</adf-cloud-form>
```
### Custom form outcomes template
You can set the custom form outcomes using an `<adf-cloud-form-custom-outcomes>` element.
```html
<adf-cloud-form .... >
<adf-cloud-form-custom-outcomes>
<button mat-button (click)="onCustomOutcome1()">
Custom-outcome-1
</button>
<button mat-button (click)="onCustomOutcome2()">
Custom-outcome-2
</button>
<button mat-button (click)="onCustomOutcome3()">
Custom-outcome-3
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>
```
### Empty form template
The template defined inside `empty-form` will be shown when no form definition is found:

View File

@ -0,0 +1,24 @@
/*!
* @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 } from '@angular/core';
@Component({
selector: 'adf-cloud-form-custom-outcomes',
template: '<ng-content></ng-content>'
})
export class FormCustomOutcomesComponent {}

View File

@ -35,6 +35,7 @@
</adf-form-renderer>
</mat-card-content>
<mat-card-actions *ngIf="form.hasOutcomes()" class="adf-form-mat-card-actions">
<ng-content select="adf-cloud-form-custom-outcomes"></ng-content>
<button [id]="'adf-form-'+ outcome.name | formatSpace" *ngFor="let outcome of form.outcomes"
[color]="getColorForOutcome(outcome.name)" mat-button [disabled]="!isOutcomeButtonEnabled(outcome)"
[class.adf-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"

View File

@ -15,9 +15,12 @@
* limitations under the License.
*/
import { SimpleChange } from '@angular/core';
import { SimpleChange, DebugElement, CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs';
import { FormFieldModel, FormFieldTypes, FormService, FormOutcomeEvent, FormOutcomeModel, LogService, WidgetVisibilityService } from '@alfresco/adf-core';
import { FormFieldModel, FormFieldTypes, FormService, FormOutcomeEvent, FormOutcomeModel, LogService, WidgetVisibilityService, setupTestBed } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudService } from '../services/form-cloud.service';
import { FormCloudComponent } from './form-cloud.component';
import { FormCloud } from '../models/form-cloud.model';
@ -758,52 +761,61 @@ 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
});
@Component({
selector: 'adf-form-cloud-with-custom-outcomes',
template: `
<adf-cloud-form #adfCloudForm>
<adf-cloud-form-custom-outcomes>
<button mat-button id="adf-custom-outcome-1" (click)="onButtonClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onButtonClick()">
CUSTOM-BUTTON-2
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
formComponent.form = formModel;
formComponent.executeOutcome.subscribe(() => {
done();
});
class FormCloudWithCustomOutComesComponent {
formComponent.onOutcomeClicked(outcome);
onButtonClick() {}
}
describe('FormCloudWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormCloudWithCustomOutComesComponent>;
let component: FormCloudWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({
imports: [ProcessServiceCloudTestingModule],
declarations: [FormCloudWithCustomOutComesComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
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);
beforeEach(() => {
fixture = TestBed.createComponent(FormCloudWithCustomOutComesComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});
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
});
afterEach(() => {
fixture.destroy();
});
formComponent.form = formModel;
formComponent.executeOutcome.subscribe(() => {
done();
});
it('should create instance of FormCloudWithCustomOutComesComponent', () => {
expect(component instanceof FormCloudWithCustomOutComesComponent).toBe(true, 'should create FormCloudWithCustomOutComesComponent');
});
formComponent.onOutcomeClicked(outcome);
it('should be able to inject custom outcomes and click on custom outcomes', () => {
fixture.detectChanges();
const cancelSpy = spyOn(component, 'onButtonClick').and.callThrough();
const cancelBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
cancelBtn.nativeElement.click();
expect(cancelSpy).toHaveBeenCalled();
expect(cancelBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1');
});
});

View File

@ -53,18 +53,6 @@ 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>();
@ -168,7 +156,6 @@ 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;
@ -190,7 +177,6 @@ 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;
@ -333,39 +319,4 @@ 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

@ -25,6 +25,7 @@ import { MaterialModule } from '../material.module';
import { FormCloudComponent } from './components/form-cloud.component';
import { FormDefinitionSelectorCloudComponent } from './components/form-definition-selector-cloud.component';
import { FormDefinitionSelectorCloudService } from './services/form-definition-selector-cloud.service';
import { FormCustomOutcomesComponent } from './components/form-cloud-custom-outcomes.component';
@NgModule({
imports: [
@ -38,13 +39,13 @@ import { FormDefinitionSelectorCloudService } from './services/form-definition-s
FormBaseModule,
CoreModule
],
declarations: [FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent],
declarations: [FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent, FormCustomOutcomesComponent],
providers: [FormDefinitionSelectorCloudService],
entryComponents: [
UploadCloudWidgetComponent
],
exports: [
FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent
FormCloudComponent, UploadCloudWidgetComponent, FormDefinitionSelectorCloudComponent, FormCustomOutcomesComponent
]
})
export class FormCloudModule {

View File

@ -28,10 +28,6 @@ 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

@ -7,13 +7,20 @@
[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-custom-outcomes>
<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>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>
<ng-template #withoutForm>

View File

@ -15,17 +15,17 @@
* limitations under the License.
*/
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA, SimpleChange, Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { setupTestBed, IdentityUserService } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { TaskCloudModule } from '../../task-cloud.module';
import { TaskDirectiveModule } from '../../directives/task-directive.module';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { TaskFormCloudComponent } from './task-form-cloud.component';
import { setupTestBed, IdentityUserService } from '@alfresco/adf-core';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
import { TaskCloudService } from '../../services/task-cloud.service';
import { of } from 'rxjs';
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
const taskDetails = {
appName: 'simple-app',
@ -348,5 +348,94 @@ describe('TaskFormCloudComponent', () => {
});
});
});
@Component({
selector: 'adf-task-form-cloud-with-custom-outcomes',
template: `
<adf-cloud-form #adfCloudForm>
<adf-cloud-form-custom-outcomes>
<button mat-button *ngIf="showCancelButton" id="adf-cloud-cancel-task" (click)="onCancel()">
CANCEL
</button>
<button mat-button *ngIf="canClaimTask()" adf-cloud-claim-task [appName]="appName" [taskId]="taskId" (click)="onClaim()">
CLAIM
</button>
<button mat-button *ngIf="canUnclaimTask()" adf-cloud-unclaim-task [appName]="appName" [taskId]="taskId" (click)="onUnclaim()">
UNCLAIM
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
class TaskFormWithCustomOutComesComponent {
appName = 'simple-app';
taskId = 'mock-task-id';
showCancelButton = true;
canClaimTask() { return true; }
canUnclaimTask() { return true; }
onUnclaim() {}
onClaim() {}
onCancel() {}
}
describe('TaskFormWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<TaskFormWithCustomOutComesComponent>;
let component: TaskFormWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({
imports: [ProcessServiceCloudTestingModule, TaskCloudModule, TaskDirectiveModule],
declarations: [TaskFormWithCustomOutComesComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskFormWithCustomOutComesComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should create instance of TaskFormWithCustomOutComesComponent', () => {
expect(component instanceof TaskFormWithCustomOutComesComponent).toBe(true, 'should create TaskFormWithCustomOutComesComponent');
});
it('should be able to display and click on cancel button', () => {
fixture.detectChanges();
const cancelSpy = spyOn(component, 'onCancel').and.callThrough();
const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
cancelBtn.nativeElement.click();
expect(cancelSpy).toHaveBeenCalled();
expect(cancelBtn.nativeElement.innerText).toBe('CANCEL');
});
it('should be able to display and click on claim button', () => {
fixture.detectChanges();
const claimSpy = spyOn(component, 'onClaim').and.callThrough();
const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
claimBtn.nativeElement.click();
expect(claimSpy).toHaveBeenCalled();
expect(claimBtn.nativeElement.innerText).toBe('CLAIM');
});
it('should be able to display and click on unclaim button', () => {
fixture.detectChanges();
const unClaimSpy = spyOn(component, 'onUnclaim').and.callThrough();
const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
unclaimBtn.nativeElement.click();
expect(unClaimSpy).toHaveBeenCalled();
expect(unclaimBtn.nativeElement.innerText).toBe('UNCLAIM');
});
});

View File

@ -22,7 +22,6 @@ import {
import { FormCloud } from '../../../form/models/form-cloud.model';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
import { TaskCloudService } from '../../services/task-cloud.service';
import { IdentityUserService, FormOutcomeModel } from '@alfresco/adf-core';
@Component({
selector: 'adf-cloud-task-form',
@ -90,8 +89,7 @@ export class TaskFormCloudComponent implements OnChanges {
taskDetails: TaskDetailsCloudModel;
constructor(
private taskCloudService: TaskCloudService,
private identityUserService: IdentityUserService) {
private taskCloudService: TaskCloudService) {
}
ngOnChanges(changes: SimpleChanges) {
@ -132,7 +130,7 @@ export class TaskFormCloudComponent implements OnChanges {
}
isReadOnly(): boolean {
return this.readOnly || this.taskDetails.isCompleted();
return this.readOnly || !this.taskCloudService.canCompleteTask(this.taskDetails);
}
onCompleteTask() {
@ -151,31 +149,6 @@ export class TaskFormCloudComponent implements OnChanges {
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);
}