[ACA-3416] Add Claim/Release actions on adf task form (#5753)

* [ACA-3255] FE - Claim a task

* * Added unit tests

* * Added unit tests
* Changed cloud directive names

* * Added/Updated documents

* * Added showReleaseClaim button flag
* Add unit test too

* * Used claim/release directive in task-header component.

* * Fixed unit test

* * Fixed one comment

* * After rebase

* * Fixed comments
This commit is contained in:
siva kumar 2020-06-10 15:13:23 +05:30 committed by GitHub
parent 77bbecea8e
commit ea62b1e3bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 947 additions and 105 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,18 +1,18 @@
--- ---
Title: Claim Task Directive Title: Claim Task Cloud Directive
Added: v3.1.0 Added: v3.9.0
Status: Experimental Status: Experimental
Last reviewed: 2019-03-25 Last reviewed: 2020-06-09
--- ---
# [Claim task directive](../../../lib/process-services-cloud/src/lib/task/directives/claim-task.directive.ts "Defined in claim-task.directive.ts") # [Claim task Cloud directive](../../../lib/process-services-cloud/src/lib/task/directives/claim-task-cloud.directive.ts "Defined in claim-task-cloud.directive.ts")
Claims a task Claims a task
## Basic Usage ## Basic Usage
```html ```html
<button adf-claim-task [appName]="appName" [taskId]="taskId" (success)="onTaskClaimed()">Complete</button> <button adf-cloud-claim-task [appName]="appName" [taskId]="taskId" (success)="onTaskClaimed()">Claim</button>
``` ```
## Class members ## Class members
@ -22,7 +22,7 @@ Claims a task
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| appName | `string` | "" | (Required) The name of the application. | | appName | `string` | "" | (Required) The name of the application. |
| taskId | `string` | | (Required) The id of the task. | | taskId | `string` | "" | (Required) The id of the task. |
### Events ### Events

View File

@ -0,0 +1,32 @@
---
Title: Unclaim Task Cloud Directive
Added: v3.9.0
Status: Experimental
Last reviewed: 2020-06-09
---
# [Unclaim Task Cloud directive](../../../lib/process-services-cloud/src/lib/task/directives/unclaim-task-cloud.directive.ts "Defined in unclaim-task-cloud.directive.ts")
Unclaims a task
## Basic Usage
```html
<button adf-cloud-unclaim-task [appName]="appName" [taskId]="taskId" (success)="onTaskUnclaimed()">Unclaim</button>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| appName | `string` | "" | (Required) The name of the application. |
| taskId | `string` | "" | (Required) The id of the task. |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task cannot be completed. |
| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task is completed. |

View File

@ -0,0 +1,34 @@
---
Title: Form custom outcomes component
Added: v3.9.0
Status: Active
Last reviewed: 2020-06-09
---
# [Form custom outcomes component](../../../lib/process-services/src/lib/form/form-custom-outcomes.component.ts "Defined in form-custom-outcomes.component.ts")
Supplies custom outcome buttons to be included in [Form component](form.component.md).
![](../../docassets/images/form-custom-outcomes.component.png)
## Basic Usage
```html
<adf-form>
<adf-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-form-custom-outcomes>
</adf-form>
```
## See Also
- [Form component](form.component.md)

View File

@ -47,6 +47,8 @@ Shows a [`form`](../../../lib/process-services/src/lib/task-list/models/form.mod
| formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is loaded or reloaded. | | formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is loaded or reloaded. |
| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. | | formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. |
| showAttachForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the form associated with the form task is attached. | | showAttachForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<void>` | Emitted when the form associated with the form task is attached. |
| 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 (ie, requeued). |
## See also ## See also

View File

@ -27,6 +27,7 @@ Shows all the information related to a task.
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| formName | `string` | null | The name of the form. | | formName | `string` | null | The name of the form. |
| taskDetails | [`TaskDetailsModel`](../../../lib/process-services/src/lib/task-list/models/task-details.model.ts) | | (required) Details related to the task. | | taskDetails | [`TaskDetailsModel`](../../../lib/process-services/src/lib/task-list/models/task-details.model.ts) | | (required) Details related to the task. |
| showClaimRelease | `boolean` | true | Toggles display of the claim/release button. |
### Events ### Events

View File

@ -0,0 +1,31 @@
---
Title: Claim Task Directive
Added: v3.9.0
Status: Experimental
Last reviewed: 2020-06-09
---
# [Claim task directive](../../../lib/process-services/src/lib/task-list/components/task-form/claim-task.directive.ts "Defined in claim-task.directive.ts")
Claims a task
## Basic Usage
```html
<button adf-claim-task [taskId]="taskId" (success)="onTaskClaimed()">Claim</button>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| taskId | `string` | "" | (Required) The id of the task. |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task cannot be completed. |
| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task is completed. |

View File

@ -1,18 +1,18 @@
--- ---
Title: Unclaim Task Directive Title: Unclaim Task Directive
Added: v3.1.0 Added: v3.9.0
Status: Experimental Status: Experimental
Last reviewed: 2019-03-25 Last reviewed: 2020-06-09
--- ---
# [Unclaim task directive](../../../lib/process-services-cloud/src/lib/task/directives/unclaim-task.directive.ts "Defined in unclaim-task.directive.ts") # [Unclaim Task directive](../../../lib/process-services/src/lib/task-list/components/task-form/unclaim-task.directive.ts "Defined in unclaim-task.directive.ts")
Unclaims a task Unclaims a task
## Basic Usage ## Basic Usage
```html ```html
<button adf-unclaim-task [appName]="appName" [taskId]="taskId" (success)="onTaskUnclaimed()">Complete</button> <button adf-unclaim-task [appName]="appName" [taskId]="taskId" (success)="onTaskUnclaimed()">Unclaim</button>
``` ```
## Class members ## Class members
@ -21,8 +21,7 @@ Unclaims a task
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| appName | `string` | "" | (Required) The name of the application. | | taskId | `string` | "" | (Required) The id of the task. |
| taskId | `string` | | (Required) The id of the task. |
### Events ### Events

View File

@ -19,12 +19,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { TaskCloudService } from '../services/task-cloud.service'; import { TaskCloudService } from '../services/task-cloud.service';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ClaimTaskDirective } from './claim-task.directive'; import { ClaimTaskCloudDirective } from './claim-task-cloud.directive';
import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock'; import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('ClaimTaskDirective', () => { describe('ClaimTaskCloudDirective', () => {
@Component({ @Component({
selector: 'adf-cloud-claim-test-component', selector: 'adf-cloud-claim-test-component',
@ -35,8 +35,8 @@ describe('ClaimTaskDirective', () => {
taskMock = 'test1234'; taskMock = 'test1234';
appNameMock = 'simple-app'; appNameMock = 'simple-app';
@ViewChild(ClaimTaskDirective) @ViewChild(ClaimTaskCloudDirective)
claimTaskDirective: ClaimTaskDirective; claimTaskDirective: ClaimTaskCloudDirective;
} }
let fixture: ComponentFixture<TestComponent>; let fixture: ComponentFixture<TestComponent>;
@ -78,8 +78,8 @@ describe('Claim Task Directive validation errors', () => {
appNameUndefined = undefined; appNameUndefined = undefined;
appNameNull = null; appNameNull = null;
@ContentChildren(ClaimTaskDirective) @ContentChildren(ClaimTaskCloudDirective)
claimTaskValidationDirective: ClaimTaskDirective; claimTaskValidationDirective: ClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -90,8 +90,8 @@ describe('Claim Task Directive validation errors', () => {
appName = 'simple-app'; appName = 'simple-app';
@ContentChildren(ClaimTaskDirective) @ContentChildren(ClaimTaskCloudDirective)
claimTaskValidationDirective: ClaimTaskDirective; claimTaskValidationDirective: ClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -103,8 +103,8 @@ describe('Claim Task Directive validation errors', () => {
appNameUndefined = undefined; appNameUndefined = undefined;
taskMock = 'test1234'; taskMock = 'test1234';
@ContentChildren(ClaimTaskDirective) @ContentChildren(ClaimTaskCloudDirective)
claimTaskValidationDirective: ClaimTaskDirective; claimTaskValidationDirective: ClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -116,8 +116,8 @@ describe('Claim Task Directive validation errors', () => {
appNameNull = null; appNameNull = null;
taskMock = 'test1234'; taskMock = 'test1234';
@ViewChild(ClaimTaskDirective) @ViewChild(ClaimTaskCloudDirective)
claimTaskValidationDirective: ClaimTaskDirective; claimTaskValidationDirective: ClaimTaskCloudDirective;
} }
let fixture: ComponentFixture<any>; let fixture: ComponentFixture<any>;

View File

@ -22,7 +22,7 @@ import { TaskCloudService } from '../services/task-cloud.service';
// tslint:disable-next-line: directive-selector // tslint:disable-next-line: directive-selector
selector: '[adf-cloud-claim-task]' selector: '[adf-cloud-claim-task]'
}) })
export class ClaimTaskDirective implements OnInit { export class ClaimTaskCloudDirective implements OnInit {
/** (Required) The id of the task. */ /** (Required) The id of the task. */
@Input() @Input()

View File

@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './claim-task.directive'; export * from './claim-task-cloud.directive';
export * from './unclaim-task.directive'; export * from './unclaim-task-cloud.directive';
export * from './complete-task.directive'; export * from './complete-task.directive';
export * from './task-directive.module'; export * from './task-directive.module';

View File

@ -17,19 +17,19 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CompleteTaskDirective } from './complete-task.directive'; import { CompleteTaskDirective } from './complete-task.directive';
import { ClaimTaskDirective } from './claim-task.directive'; import { ClaimTaskCloudDirective } from './claim-task-cloud.directive';
import { UnClaimTaskDirective } from './unclaim-task.directive'; import { UnClaimTaskCloudDirective } from './unclaim-task-cloud.directive';
@NgModule({ @NgModule({
declarations: [ declarations: [
CompleteTaskDirective, CompleteTaskDirective,
ClaimTaskDirective, ClaimTaskCloudDirective,
UnClaimTaskDirective UnClaimTaskCloudDirective
], ],
exports: [ exports: [
CompleteTaskDirective, CompleteTaskDirective,
ClaimTaskDirective, ClaimTaskCloudDirective,
UnClaimTaskDirective UnClaimTaskCloudDirective
] ]
}) })
export class TaskDirectiveModule { } export class TaskDirectiveModule { }

View File

@ -19,12 +19,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { TaskCloudService } from '../services/task-cloud.service'; import { TaskCloudService } from '../services/task-cloud.service';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { UnClaimTaskDirective } from './unclaim-task.directive'; import { UnClaimTaskCloudDirective } from './unclaim-task-cloud.directive';
import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock'; import { taskClaimCloudMock } from '../task-header/mocks/fake-claim-task.mock';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('UnClaimTaskDirective', () => { describe('UnClaimTaskCloudDirective', () => {
@Component({ @Component({
selector: 'adf-cloud-test-component', selector: 'adf-cloud-test-component',
@ -35,8 +35,8 @@ describe('UnClaimTaskDirective', () => {
appName = 'simple-app'; appName = 'simple-app';
taskIdMock = '1234'; taskIdMock = '1234';
@ContentChildren(UnClaimTaskDirective) @ContentChildren(UnClaimTaskCloudDirective)
unclaimTaskDirective: UnClaimTaskDirective; unclaimTaskDirective: UnClaimTaskCloudDirective;
} }
let fixture: ComponentFixture<TestComponent>; let fixture: ComponentFixture<TestComponent>;
@ -78,8 +78,8 @@ describe('UnClaim Task Directive validation errors', () => {
appNameUndefined = undefined; appNameUndefined = undefined;
appNameNull = null; appNameNull = null;
@ContentChildren(UnClaimTaskDirective) @ContentChildren(UnClaimTaskCloudDirective)
claimTaskValidationDirective: UnClaimTaskDirective; claimTaskValidationDirective: UnClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -90,8 +90,8 @@ describe('UnClaim Task Directive validation errors', () => {
appName = 'simple-app'; appName = 'simple-app';
@ContentChildren(UnClaimTaskDirective) @ContentChildren(UnClaimTaskCloudDirective)
claimTaskValidationDirective: UnClaimTaskDirective; claimTaskValidationDirective: UnClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -103,8 +103,8 @@ describe('UnClaim Task Directive validation errors', () => {
appNameUndefined = undefined; appNameUndefined = undefined;
taskMock = 'test1234'; taskMock = 'test1234';
@ContentChildren(UnClaimTaskDirective) @ContentChildren(UnClaimTaskCloudDirective)
claimTaskValidationDirective: UnClaimTaskDirective; claimTaskValidationDirective: UnClaimTaskCloudDirective;
} }
@Component({ @Component({
@ -116,8 +116,8 @@ describe('UnClaim Task Directive validation errors', () => {
appNameNull = null; appNameNull = null;
taskMock = 'test1234'; taskMock = 'test1234';
@ViewChild(UnClaimTaskDirective) @ViewChild(UnClaimTaskCloudDirective)
claimTaskValidationDirective: UnClaimTaskDirective; claimTaskValidationDirective: UnClaimTaskCloudDirective;
} }
let fixture: ComponentFixture<any>; let fixture: ComponentFixture<any>;

View File

@ -21,7 +21,7 @@ import { TaskCloudService } from '../services/task-cloud.service';
// tslint:disable-next-line: directive-selector // tslint:disable-next-line: directive-selector
selector: '[adf-cloud-unclaim-task]' selector: '[adf-cloud-unclaim-task]'
}) })
export class UnClaimTaskDirective implements OnInit { export class UnClaimTaskCloudDirective implements OnInit {
/** (Required) The id of the task. */ /** (Required) The id of the task. */
@Input() @Input()

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-form-custom-outcomes',
template: '<ng-content></ng-content>'
})
export class FormCustomOutcomesComponent {}

View File

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

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { SimpleChange, ComponentFactoryResolver, Injector, NgModule, Component } from '@angular/core'; import { SimpleChange, ComponentFactoryResolver, Injector, NgModule, Component, ViewChild, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel, import { FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel,
@ -1013,3 +1014,79 @@ describe('FormComponent', () => {
expect(radioFieldById.value).toBe('option_3'); expect(radioFieldById.value).toBe('option_3');
}); });
}); });
@Component({
selector: 'adf-form-with-custom-outcomes',
template: `
<adf-form #adfForm>
<adf-form-custom-outcomes>
<button mat-button id="adf-custom-outcome-1" (click)="onCustomButtonOneClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onCustomButtonTwoClick()">
CUSTOM-BUTTON-2
</button>
</adf-form-custom-outcomes>
</adf-form>`
})
class FormWithCustomOutComesComponent {
@ViewChild('adfForm')
adfForm: FormComponent;
onCustomButtonOneClick() { }
onCustomButtonTwoClick() { }
}
describe('FormWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormWithCustomOutComesComponent>;
let customComponent: FormWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessTestingModule
],
declarations: [FormWithCustomOutComesComponent]
});
beforeEach(() => {
fixture = TestBed.createComponent(FormWithCustomOutComesComponent);
customComponent = fixture.componentInstance;
debugElement = fixture.debugElement;
const formRepresentation = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'outcome-1', name: 'outcome 1' }
]
};
const form = new FormModel(formRepresentation);
customComponent.adfForm.form = form;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should be able to inject custom outcomes and click on custom outcomes', () => {
const onCustomButtonOneSpy = spyOn(customComponent, 'onCustomButtonOneClick').and.callThrough();
const buttonOneBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
const buttonTwoBtn = debugElement.query(By.css('#adf-custom-outcome-2'));
expect(buttonOneBtn).not.toBeNull();
expect(buttonTwoBtn).not.toBeNull();
buttonOneBtn.nativeElement.click();
expect(onCustomButtonOneSpy).toHaveBeenCalled();
expect(buttonOneBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1');
expect(buttonTwoBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-2');
});
});

View File

@ -20,6 +20,7 @@ import { MaterialModule } from '../material.module';
import { CoreModule } from '@alfresco/adf-core'; import { CoreModule } from '@alfresco/adf-core';
import { FormComponent } from './form.component'; import { FormComponent } from './form.component';
import { StartFormComponent } from './start-form.component'; import { StartFormComponent } from './start-form.component';
import { FormCustomOutcomesComponent } from './form-custom-outcomes.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -28,11 +29,13 @@ import { StartFormComponent } from './start-form.component';
], ],
declarations: [ declarations: [
FormComponent, FormComponent,
StartFormComponent StartFormComponent,
FormCustomOutcomesComponent
], ],
exports: [ exports: [
FormComponent, FormComponent,
StartFormComponent StartFormComponent,
FormCustomOutcomesComponent
] ]
}) })
export class FormModule {} export class FormModule {}

View File

@ -18,4 +18,5 @@
export * from './form.component'; export * from './form.component';
export * from './start-form.component'; export * from './start-form.component';
export * from './process-form-rendering.service'; export * from './process-form-rendering.service';
export * from './form-custom-outcomes.component';
export * from './form.module'; export * from './form.module';

View File

@ -35,6 +35,8 @@
(completed)="onComplete()" (completed)="onComplete()"
(showAttachForm)="onShowAttachForm()" (showAttachForm)="onShowAttachForm()"
(executeOutcome)='onFormExecuteOutcome($event)' (executeOutcome)='onFormExecuteOutcome($event)'
(taskClaimed)="onClaimAction($event)"
(taskUnclaimed)="onUnclaimAction($event)"
(error)="onFormError($event)" #activitiTaskForm> (error)="onFormError($event)" #activitiTaskForm>
</adf-task-form> </adf-task-form>
<adf-attach-form *ngIf="isShowAttachForm()" <adf-attach-form *ngIf="isShowAttachForm()"

View File

@ -0,0 +1,122 @@
/*!
* @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, Output, EventEmitter } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { TaskListService } from '../../services/tasklist.service';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
describe('ClaimTaskDirective', () => {
@Component({
selector: 'adf-claim-test-component',
template: '<button adf-claim-task [taskId]="taskId" (success)="onClaim($event)">Claim</button>'
})
class TestComponent {
taskId = 'test1234';
@Output()
claim: EventEmitter<any> = new EventEmitter<any>();
onClaim(event) {
this.claim.emit(event);
}
}
let fixture: ComponentFixture<TestComponent>;
let taskListService: TaskListService;
setupTestBed({
imports: [
ProcessTestingModule
],
declarations: [
TestComponent
]
});
beforeEach(() => {
taskListService = TestBed.get(TaskListService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('Should be able to call claim task service', () => {
const claimTaskSpy = spyOn(taskListService, 'claimTask').and.returnValue(of({}));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(claimTaskSpy).toHaveBeenCalledWith(fixture.componentInstance.taskId);
});
it('Should be able to catch success event on click of claim button', async() => {
spyOn(taskListService, 'claimTask').and.returnValue(of({}));
const unclaimSpy = spyOn(fixture.componentInstance.claim, 'emit');
const button = fixture.nativeElement.querySelector('button');
button.click();
fixture.detectChanges();
await fixture.whenStable();
expect(unclaimSpy).toHaveBeenCalledWith(fixture.componentInstance.taskId);
});
});
describe('Claim Task Directive validation errors', () => {
@Component({
selector: 'adf-claim-no-fields-validation-component',
template: '<button adf-claim-task></button>'
})
class ClaimTestMissingInputDirectiveComponent { }
@Component({
selector: 'adf-claim-no-taskid-validation-component',
template: '<button adf-claim-task [taskId]=""></button>'
})
class ClaimTestMissingTaskIdDirectiveComponent { }
let fixture: ComponentFixture<any>;
setupTestBed({
imports: [
ProcessTestingModule
],
declarations: [
ClaimTestMissingTaskIdDirectiveComponent,
ClaimTestMissingInputDirectiveComponent
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent);
});
it('should throw error when missing input', () => {
fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent);
expect(() => fixture.detectChanges()).toThrowError();
});
it('should throw error when taskId is not set', () => {
fixture = TestBed.createComponent(ClaimTestMissingTaskIdDirectiveComponent);
expect( () => fixture.detectChanges()).toThrowError('Attribute taskId is required');
});
});

View File

@ -0,0 +1,89 @@
/*!
* @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 {
Directive,
Input,
Output,
EventEmitter,
HostListener
} from '@angular/core';
import { TaskListService } from '../../services/tasklist.service';
import { LogService } from '@alfresco/adf-core';
@Directive({
// tslint:disable-next-line: directive-selector
selector: '[adf-claim-task]'
})
export class ClaimTaskDirective {
/** (Required) The id of the task. */
@Input()
taskId: string;
/** Emitted when the task is claimed. */
@Output()
success: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the task cannot be claimed. */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
invalidParams: string[] = [];
constructor(
private taskListService: TaskListService,
private logService: LogService) {}
ngOnInit() {
this.validateInputs();
}
validateInputs() {
if (!this.isTaskValid()) {
this.invalidParams.push('taskId');
}
if (this.invalidParams.length) {
throw new Error(
`Attribute ${this.invalidParams.join(', ')} is required`
);
}
}
isTaskValid(): boolean {
return this.taskId && this.taskId.length > 0;
}
@HostListener('click')
async onClick() {
try {
this.claimTask();
} catch (error) {
this.error.emit(error);
}
}
private async claimTask() {
await this.taskListService.claimTask(this.taskId).subscribe(
() => {
this.logService.info('Task claimed');
this.success.emit(this.taskId);
},
error => this.error.emit(error)
);
}
}

View File

@ -16,6 +16,10 @@
(formError)='onFormError($event)' (formError)='onFormError($event)'
(error)='onError($event)' (error)='onError($event)'
(executeOutcome)='onFormExecuteOutcome($event)'> (executeOutcome)='onFormExecuteOutcome($event)'>
<adf-form-custom-outcomes>
<ng-template [ngTemplateOutlet]="taskFormButtons">
</ng-template>
</adf-form-custom-outcomes>
</adf-form> </adf-form>
<ng-template #withoutForm> <ng-template #withoutForm>
<adf-task-standalone *ngIf="isStandaloneTask(); else emptyFormMessage" <adf-task-standalone *ngIf="isStandaloneTask(); else emptyFormMessage"
@ -56,6 +60,7 @@
</ng-template> </ng-template>
</mat-card-content> </mat-card-content>
<mat-card-actions class="adf-task-form-actions"> <mat-card-actions class="adf-task-form-actions">
<ng-template [ngTemplateOutlet]="taskFormButtons"></ng-template>
<button id="adf-no-form-cancel-button" mat-button *ngIf="showCancelButton" (click)="onCancel()"> <button id="adf-no-form-cancel-button" mat-button *ngIf="showCancelButton" (click)="onCancel()">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}} {{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
</button> </button>
@ -66,6 +71,23 @@
</mat-card> </mat-card>
</ng-template> </ng-template>
</ng-template> </ng-template>
<ng-template #taskFormButtons>
<button mat-button data-automation-id="adf-task-form-claim-button"
*ngIf="isTaskClaimable()"
adf-claim-task
[taskId]="taskId"
(success)="onClaimTask($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
</button>
<button mat-button data-automation-id="adf-task-form-unclaim-button"
*ngIf="isTaskClaimedByCandidateMember()"
adf-unclaim-task
[taskId]="taskId"
(success)="onUnclaimTask($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
</button>
</ng-template>
</ng-container> </ng-container>
<ng-template #loadingTemplate> <ng-template #loadingTemplate>
<div fxLayout="row" fxLayoutAlign="center stretch"> <div fxLayout="row" fxLayoutAlign="center stretch">

View File

@ -37,11 +37,15 @@ import {
standaloneTaskWithoutForm, standaloneTaskWithoutForm,
completedStandaloneTaskWithoutForm, completedStandaloneTaskWithoutForm,
claimableTaskDetailsMock, claimableTaskDetailsMock,
initiatorCanCompleteTaskDetailsMock initiatorCanCompleteTaskDetailsMock,
taskDetailsWithOutCandidateGroup,
claimedTaskDetailsMock,
claimedByGroupMemberMock
} from '../../../mock/task/task-details.mock'; } from '../../../mock/task/task-details.mock';
import { TaskDetailsModel } from '../../models/task-details.model'; import { TaskDetailsModel } from '../../models/task-details.model';
import { ProcessTestingModule } from '../../../testing/process.testing.module'; import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
describe('TaskFormComponent', () => { describe('TaskFormComponent', () => {
let component: TaskFormComponent; let component: TaskFormComponent;
@ -495,4 +499,125 @@ describe('TaskFormComponent', () => {
expect(validationForm.textContent).toBe('check_circle'); expect(validationForm.textContent).toBe('check_circle');
}); });
}); });
describe('Claim/Unclaim buttons', () => {
it('should display the claim button if no assignee', async() => {
getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-claim-button"]'));
expect(claimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.CLAIM');
});
it('should not display the claim/requeue button if the task is not claimable ', async() => {
getTaskDetailsSpy.and.returnValue(of(taskDetailsWithOutCandidateGroup));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-claim-button"]'));
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-unclaim-button"]'));
expect(component.isTaskClaimable()).toBe(false);
expect(component.isTaskClaimedByCandidateMember()).toBe(false);
expect(unclaimButton).toBeNull();
expect(claimButton).toBeNull();
});
it('should display the claim button if the task is claimable', async() => {
getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-claim-button"]'));
expect(component.isTaskClaimable()).toBe(true);
expect(claimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.CLAIM');
});
it('should display the release button if task is claimed by the current logged-in user', async() => {
getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee));
getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-unclaim-button"]'));
expect(component.isTaskClaimedByCandidateMember()).toBe(true);
expect(unclaimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM');
});
it('should not display the release button to logged in user if task is claimed by other candidate member', async() => {
getTaskDetailsSpy.and.returnValue(of(claimedByGroupMemberMock));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-unclaim-button"]'));
expect(component.isTaskClaimedByCandidateMember()).toBe(false);
expect(unclaimButton).toBeNull();
});
it('should not display the release button if the task is completed', async() => {
getTaskDetailsSpy.and.returnValue(of(completedTaskDetailsMock));
component.taskId = 'mock-task-id';
fixture.detectChanges();
await fixture.whenStable();
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-claim-button"]'));
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="adf-task-form-unclaim-button"]'));
expect(claimButton).toBeNull();
expect(unclaimButton).toBeNull();
});
it('should emit taskClaimed when task is claimed', (done) => {
spyOn(taskListService, 'claimTask').and.returnValue(of({}));
getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock));
component.taskId = 'mock-task-id';
component.taskClaimed.subscribe((taskId) => {
expect(taskId).toEqual(component.taskId);
done();
});
component.ngOnInit();
fixture.detectChanges();
const claimBtn = fixture.debugElement.query(By.css('[adf-claim-task]'));
claimBtn.nativeElement.click();
});
it('should emit taskUnClaimed when task is unclaimed', (done) => {
spyOn(taskListService, 'unclaimTask').and.returnValue(of({}));
getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee));
getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock));
component.taskId = 'mock-task-id';
component.taskUnclaimed.subscribe((taskId: string) => {
expect(taskId).toEqual(component.taskId);
done();
});
component.ngOnInit();
fixture.detectChanges();
const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]'));
unclaimBtn.nativeElement.click();
});
});
}); });

View File

@ -117,6 +117,14 @@ export class TaskFormComponent implements OnInit {
@Output() @Output()
cancel = new EventEmitter<void>(); cancel = new EventEmitter<void>();
/** Emitted when the task is claimed. */
@Output()
taskClaimed = new EventEmitter<string>();
/** Emitted when the task is unclaimed (ie, requeued).. */
@Output()
taskUnclaimed = new EventEmitter<string>();
taskDetails: TaskDetailsModel; taskDetails: TaskDetailsModel;
currentLoggedUser: UserRepresentation; currentLoggedUser: UserRepresentation;
loading: boolean = false; loading: boolean = false;
@ -278,4 +286,28 @@ export class TaskFormComponent implements OnInit {
getCompletedTaskTranslatedMessage(): Observable<string> { getCompletedTaskTranslatedMessage(): Observable<string> {
return this.translationService.get('ADF_TASK_FORM.COMPLETED_TASK.TITLE', { taskName: this.taskDetails.name }); return this.translationService.get('ADF_TASK_FORM.COMPLETED_TASK.TITLE', { taskName: this.taskDetails.name });
} }
isCandidateMember(): boolean {
return this.taskDetails.managerOfCandidateGroup || this.taskDetails.memberOfCandidateGroup || this.taskDetails.memberOfCandidateUsers;
}
isTaskClaimable(): boolean {
return this.isCandidateMember() && !this.isAssigned();
}
isTaskClaimedByCandidateMember(): boolean {
return this.isCandidateMember() && this.isAssignedToMe() && !this.isCompletedTask();
}
reloadTask() {
this.loadTask(this.taskId);
}
onClaimTask(taskId: string) {
this.taskClaimed.emit(taskId);
}
onUnclaimTask(taskId: string) {
this.taskUnclaimed.emit(taskId);
}
} }

View File

@ -0,0 +1,126 @@
/*!
* @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, Output, EventEmitter } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { TaskListService } from '../../services/tasklist.service';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
describe('UnclaimTaskDirective', () => {
@Component({
selector: 'adf-unclaim-test-component',
template: '<button adf-unclaim-task [taskId]="taskId" (success)="onUnclaim($event)">Unclaim</button>'
})
class TestComponent {
taskId = 'test1234';
@Output()
unclaim: EventEmitter<any> = new EventEmitter<any>();
onUnclaim(event) {
this.unclaim.emit(event);
}
}
let fixture: ComponentFixture<TestComponent>;
let taskListService: TaskListService;
setupTestBed({
imports: [
ProcessTestingModule
],
declarations: [
TestComponent
]
});
beforeEach(() => {
taskListService = TestBed.get(TaskListService);
fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
});
it('Should be able to call unclaim task service', () => {
const claimTaskSpy = spyOn(taskListService, 'unclaimTask').and.returnValue(of({}));
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(claimTaskSpy).toHaveBeenCalledWith(fixture.componentInstance.taskId);
});
it('Should be able to catch success event on click of unclaim button', async() => {
spyOn(taskListService, 'unclaimTask').and.returnValue(of({}));
const unclaimSpy = spyOn(fixture.componentInstance.unclaim, 'emit');
const button = fixture.nativeElement.querySelector('button');
button.click();
fixture.detectChanges();
await fixture.whenStable();
expect(unclaimSpy).toHaveBeenCalledWith(fixture.componentInstance.taskId);
});
});
describe('Claim Task Directive validation errors', () => {
@Component({
selector: 'adf-unclaim-no-fields-validation-component',
template: '<button adf-unclaim-task></button>'
})
class ClaimTestMissingInputDirectiveComponent {
}
@Component({
selector: 'adf-claim-no-taskid-validation-component',
template: '<button adf-unclaim-task [taskId]=""></button>'
})
class ClaimTestMissingTaskIdDirectiveComponent {
}
let fixture: ComponentFixture<any>;
setupTestBed({
imports: [
ProcessTestingModule
],
declarations: [
ClaimTestMissingTaskIdDirectiveComponent,
ClaimTestMissingInputDirectiveComponent
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent);
});
it('should throw error when missing input', () => {
fixture = TestBed.createComponent(ClaimTestMissingInputDirectiveComponent);
expect(() => fixture.detectChanges()).toThrowError();
});
it('should throw error when taskId is not set', () => {
fixture = TestBed.createComponent(ClaimTestMissingTaskIdDirectiveComponent);
expect( () => fixture.detectChanges()).toThrowError('Attribute taskId is required');
});
});

View File

@ -0,0 +1,88 @@
/*!
* @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 {
Directive,
HostListener,
Input,
Output,
EventEmitter
} from '@angular/core';
import { TaskListService } from '../../services/tasklist.service';
import { LogService } from '@alfresco/adf-core';
@Directive({
// tslint:disable-next-line: directive-selector
selector: '[adf-unclaim-task]'
})
export class UnclaimTaskDirective {
/** (Required) The id of the task. */
@Input()
taskId: string;
/** Emitted when the task is released. */
@Output()
success: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the task cannot be released. */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
invalidParams: string[] = [];
constructor(
private taskListService: TaskListService,
private logService: LogService) {}
ngOnInit() {
this.validateInputs();
}
validateInputs() {
if (!this.isTaskValid()) {
this.invalidParams.push('taskId');
}
if (this.invalidParams.length) {
throw new Error(
`Attribute ${this.invalidParams.join(', ')} is required`
);
}
}
isTaskValid(): boolean {
return this.taskId && this.taskId.length > 0;
}
@HostListener('click')
async onClick() {
try {
this.unclaimTask();
} catch (error) {
this.error.emit(error);
}
}
private async unclaimTask() {
await this.taskListService.unclaimTask(this.taskId).subscribe(
() => {
this.logService.info('Task unclaimed');
this.success.emit(this.taskId);
},
error => this.error.emit(error)
);
}
}

View File

@ -3,10 +3,26 @@
<adf-card-view [properties]="properties" [editable]="!isCompleted()" [displayClearAction]="displayDateClearAction"></adf-card-view> <adf-card-view [properties]="properties" [editable]="!isCompleted()" [displayClearAction]="displayDateClearAction"></adf-card-view>
</mat-card-content> </mat-card-content>
<mat-card-actions class="adf-controls"> <mat-card-actions class="adf-controls" *ngIf="showClaimRelease">
<button *ngIf="isTaskClaimedByCandidateMember()" mat-button data-automation-id="header-unclaim-button" id="unclaim-task" (click)="unclaimTask(taskDetails.id)" class="adf-claim-controls">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }} <button *ngIf="isTaskClaimedByCandidateMember()"
mat-button
data-automation-id="header-unclaim-button"
id="unclaim-task"
class="adf-claim-controls"
adf-unclaim-task
[taskId]="taskDetails.id"
(success)="onUnclaimTask($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
</button> </button>
<button *ngIf="isTaskClaimable()" mat-button data-automation-id="header-claim-button" id="claim-task" (click)="claimTask(taskDetails.id)" class="adf-claim-controls">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }} <button *ngIf="isTaskClaimable()"
mat-button
data-automation-id="header-claim-button"
id="claim-task"
class="adf-claim-controls"
adf-claim-task
[taskId]="taskDetails.id"
(success)="onClaimTask($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
</button> </button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>

View File

@ -136,6 +136,30 @@ describe('TaskHeaderComponent', () => {
describe('Claiming', () => { describe('Claiming', () => {
it('should be able display the claim/release button if showClaimRelease set to true', async(() => {
component.taskDetails = new TaskDetailsModel(claimableTaskDetailsMock);
component.showClaimRelease = true;
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'));
expect(claimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.CLAIM');
});
}));
it('should not be able display the claim/release button if showClaimRelease set to false', async(() => {
component.taskDetails = new TaskDetailsModel(claimableTaskDetailsMock);
component.showClaimRelease = false;
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const claimButton = fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'));
expect(claimButton).toBeNull();
});
}));
it('should display the claim button if no assignee', async(() => { it('should display the claim button if no assignee', async(() => {
component.taskDetails = new TaskDetailsModel(claimableTaskDetailsMock); component.taskDetails = new TaskDetailsModel(claimableTaskDetailsMock);
@ -227,38 +251,37 @@ describe('TaskHeaderComponent', () => {
}); });
})); }));
it('should call the service unclaim method on un-claiming', async(() => { it('should emit claim event when task is claimed', (done) => {
spyOn(service, 'unclaimTask').and.returnValue(of(true)); spyOn(service, 'claimTask').and.returnValue(of({}));
component.taskDetails = new TaskDetailsModel(claimedTaskDetailsMock); component.taskDetails = claimableTaskDetailsMock;
component.refreshData();
component.claim.subscribe((taskId) => {
expect(taskId).toEqual(component.taskDetails.id);
done();
});
component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { const claimBtn = fixture.debugElement.query(By.css('[adf-claim-task]'));
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]')); claimBtn.nativeElement.click();
unclaimButton.triggerEventHandler('click', {}); });
expect(service.unclaimTask).toHaveBeenCalledWith('91'); it('should emit unclaim event when task is unclaimed', (done) => {
spyOn(service, 'unclaimTask').and.returnValue(of({}));
component.taskDetails = claimedTaskDetailsMock;
component.unclaim.subscribe((taskId: string) => {
expect(taskId).toEqual(component.taskDetails.id);
done();
}); });
}));
it('should trigger the unclaim event on successful un-claiming', async(() => { component.ngOnInit();
let unclaimed: boolean = false;
spyOn(service, 'unclaimTask').and.returnValue(of(true));
component.taskDetails = new TaskDetailsModel(claimedTaskDetailsMock);
component.refreshData();
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]'));
component.unclaim.subscribe(() => { unclaimBtn.nativeElement.click();
unclaimed = true; });
});
const unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
unclaimButton.triggerEventHandler('click', {});
expect(unclaimed).toBeTruthy();
});
}));
it('should display due date', async(() => { it('should display due date', async(() => {
component.taskDetails.dueDate = new Date('2016-11-03'); component.taskDetails.dueDate = new Date('2016-11-03');

View File

@ -23,12 +23,10 @@ import {
CardViewMapItemModel, CardViewMapItemModel,
CardViewTextItemModel, CardViewTextItemModel,
CardViewBaseItemModel, CardViewBaseItemModel,
LogService,
TranslationService, TranslationService,
AppConfigService AppConfigService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service';
import { TaskDescriptionValidator } from '../validators/task-description.validator'; import { TaskDescriptionValidator } from '../validators/task-description.validator';
@Component({ @Component({
@ -46,6 +44,10 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
@Input() @Input()
taskDetails: TaskDetailsModel; taskDetails: TaskDetailsModel;
/** Toggles display of the claim/release button. */
@Input()
showClaimRelease = true;
/** Emitted when the task is claimed. */ /** Emitted when the task is claimed. */
@Output() @Output()
claim: EventEmitter<any> = new EventEmitter<any>(); claim: EventEmitter<any> = new EventEmitter<any>();
@ -62,10 +64,8 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
dateFormat: string; dateFormat: string;
dateLocale: string; dateLocale: string;
constructor(private activitiTaskService: TaskListService, constructor(private bpmUserService: BpmUserService,
private bpmUserService: BpmUserService,
private translationService: TranslationService, private translationService: TranslationService,
private logService: LogService,
private appConfig: AppConfigService) { private appConfig: AppConfigService) {
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat'); this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
this.dateLocale = this.appConfig.get('dateValues.defaultDateLocale'); this.dateLocale = this.appConfig.get('dateValues.defaultDateLocale');
@ -281,28 +281,12 @@ export class TaskHeaderComponent implements OnChanges, OnInit {
return (this.taskDetails && this.taskDetails.isCompleted()) ? 'Completed' : 'Running'; return (this.taskDetails && this.taskDetails.isCompleted()) ? 'Completed' : 'Running';
} }
/** onClaimTask(taskId: string) {
* Claim task this.claim.emit(taskId);
*
* @param taskId
*/
claimTask(taskId: string) {
this.activitiTaskService.claimTask(taskId).subscribe(() => {
this.logService.info('Task claimed');
this.claim.emit(taskId);
});
} }
/** onUnclaimTask(taskId: string) {
* Unclaim task this.unclaim.emit(taskId);
*
* @param taskId
*/
unclaimTask(taskId: string) {
this.activitiTaskService.unclaimTask(taskId).subscribe(() => {
this.logService.info('Task unclaimed');
this.unclaim.emit(taskId);
});
} }
/** /**

View File

@ -21,6 +21,8 @@ export * from './components/task-header.component';
export * from './components/no-task-detail-template.directive'; export * from './components/no-task-detail-template.directive';
export * from './components/task-filters.component'; export * from './components/task-filters.component';
export * from './components/task-form/task-form.component'; export * from './components/task-form/task-form.component';
export * from './components/task-form/claim-task.directive';
export * from './components/task-form/unclaim-task.directive';
export * from './components/task-details.component'; export * from './components/task-details.component';
export * from './components/task-audit.directive'; export * from './components/task-audit.directive';
export * from './components/start-task.component'; export * from './components/start-task.component';

View File

@ -38,6 +38,8 @@ import { TaskListComponent } from './components/task-list.component';
import { TaskStandaloneComponent } from './components/task-standalone.component'; import { TaskStandaloneComponent } from './components/task-standalone.component';
import { AttachFormComponent } from './components/attach-form.component'; import { AttachFormComponent } from './components/attach-form.component';
import { FormModule } from '../form/form.module'; import { FormModule } from '../form/form.module';
import { ClaimTaskDirective } from './components/task-form/claim-task.directive';
import { UnclaimTaskDirective } from './components/task-form/unclaim-task.directive';
@NgModule({ @NgModule({
imports: [ imports: [
@ -63,7 +65,9 @@ import { FormModule } from '../form/form.module';
TaskHeaderComponent, TaskHeaderComponent,
StartTaskComponent, StartTaskComponent,
TaskStandaloneComponent, TaskStandaloneComponent,
AttachFormComponent AttachFormComponent,
ClaimTaskDirective,
UnclaimTaskDirective
], ],
exports: [ exports: [
NoTaskDetailsTemplateDirective, NoTaskDetailsTemplateDirective,
@ -76,7 +80,9 @@ import { FormModule } from '../form/form.module';
TaskHeaderComponent, TaskHeaderComponent,
StartTaskComponent, StartTaskComponent,
TaskStandaloneComponent, TaskStandaloneComponent,
AttachFormComponent AttachFormComponent,
ClaimTaskDirective,
UnclaimTaskDirective
] ]
}) })
export class TaskListModule { export class TaskListModule {