[ADF-3769] Task Header Cloud Component for APS 2 (#4140)

* [ADF-3769] Task Header Cloud Component for APS 2

* [ADF-3796] Add error catch to affected-libs.sh

* [ADF-3769] Add readOnly mode and remove selectFirstRow in Task List Cloud component
This commit is contained in:
davidcanonieto
2019-01-16 13:43:05 +00:00
committed by Eugenio Romano
parent 28da31c550
commit f2b5300705
23 changed files with 1189 additions and 53 deletions

View File

@@ -4,3 +4,9 @@
</button>
<h4>Simple page to show the taskId: {{ taskId }} of the app: {{ applicationName }}</h4>
<adf-cloud-task-header
[appName]="applicationName"
[taskId]="taskId"
[readOnly]="readOnly">
</adf-cloud-task-header>

View File

@@ -26,6 +26,7 @@ export class TaskDetailsCloudDemoComponent {
taskId: string;
applicationName: string;
readOnly = false;
constructor(private route: ActivatedRoute, private router: Router) {
this.route.params.subscribe((params) => {

View File

@@ -74,13 +74,6 @@
<input matInput class="form-control" [formControl]="taskProcessInstanceId" data-automation-id="process-instance-id">
</mat-form-field>
<mat-slide-toggle
[color]="'primary'"
(change)="toggleSelectFirstRow()"
[checked]="selectFirstRow"
data-automation-id="select-first-row">
Select First Row
</mat-slide-toggle>
<mat-slide-toggle
[color]="'primary'"
(change)="toggleMultiselect()"
@@ -111,7 +104,6 @@
[dueDate]="dueDate"
[createdDate]="createdDate"
[selectionMode]="selectionMode"
[selectFirstRow]="selectFirstRow"
[multiselect]="multiSelection"
(rowsSelected)="showSelectedRows($event)">
<data-columns>

View File

@@ -43,7 +43,6 @@ export class TaskListCloudDemoComponent implements OnInit {
createdDate: string;
dueDate: string;
selectionMode: string;
selectFirstRow: boolean = false;
multiSelection: boolean = false;
statusOptions = [
@@ -153,10 +152,6 @@ export class TaskListCloudDemoComponent implements OnInit {
this.multiSelection = !this.multiSelection;
}
toggleSelectFirstRow() {
this.selectFirstRow = !this.selectFirstRow;
}
showSelectedRows(rows: any) {
const selectedRows = rows.map((row) => {

View File

@@ -0,0 +1,52 @@
# [Task Header component](../../lib/process-services-cloud/task-header/components/task-header-cloud.component.ts "Defined in task-header.component.ts")
Shows all the information related to a task.
![adf-task-header](../docassets/images/adf-task-header.png)
## Basic Usage
```html
<adf-cloud-task-header
[appName]="applicationName"
[taskId]="taskId">
</adf-cloud-task-header>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| appName | `string` | | (required) The name of the application. |
| taskId | `string` | | (required) The id of the Task. |
| readOnly | `boolean` | false | Flag to set the component in Read Only Mode. This mode makes all the cells not clickable and not editable. |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| claim | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task is claimed. |
| unclaim | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when the task is unclaimed (ie, requeued). |
## Details
The component populates an internal array of
[CardViewModel](../core/card-view.component.md) with the information that we want to display.
By default all properties are displayed:
**_assignee_**, **_status_**, **_priority_**, **_dueDate_**, **_category_**, **_parentName_**, **_created_**, **_id_**, **_description_**, **_formName_**.
However, you can also choose which properties to show using a configuration in `app.config.json`:
```json
"adf-cloud-task-header": {
"presets": {
"properties" : [ "assignee", "status", "priority", "parentName"]
}
}
```
With this configuration, only the four listed properties will be shown.

View File

@@ -62,7 +62,6 @@ when the task list is empty:
| parentTaskId | `string` | "" | Filter the tasks. Display only tasks with parentTaskId equal to the supplied value. |
| processDefinitionId | `string` | "" | Filter the tasks. Display only tasks with processDefinitionId equal to the supplied value. |
| processInstanceId | `string` | "" | Filter the tasks. Display only tasks with processInstanceId equal to the supplied value. |
| selectFirstRow | `boolean` | true | Toggles default selection of the first row. |
| selectionMode | `string` | "single" | Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode, you can use the Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. |
| sorting | [`TaskListCloudSortingModel`](../../lib/process-services-cloud/src/lib/task/task-list/models/task-list-sorting.model.ts)`[]` | | Specifies how the table should be sorted. The parameters are for BE sorting. |
| status | `string` | "" | Filter the tasks. Display only tasks with status equal to the supplied value. |

View File

@@ -151,5 +151,36 @@
"ERROR": {
"NOT_FOUND": "No group found with the name {{groupName}}"
}
},
"ADF_CLOUD_TASK_HEADER": {
"BUTTON": {
"CLAIM": "Claim",
"UNCLAIM": "Requeue"
},
"PROPERTIES": {
"TASK_NAME": "Task",
"THUMBNAIL": "Thumbnail",
"DURATION": "Duration",
"PARENT_TASK_ID": "Parent task id",
"NAME": "Name",
"ASSIGNEE": "Assignee",
"ASSIGNEE_DEFAULT": "No assignee",
"PRIORITY": "Priority",
"DUE_DATE": "Due Date",
"DUE_DATE_DEFAULT": "No date",
"STATUS": "Status",
"CATEGORY": "Category",
"CATEGORY_DEFAULT": "No category",
"PARENT_NAME": "Parent name",
"PARENT_NAME_DEFAULT": "No parent",
"CREATED_BY": "Created By",
"CREATED": "Created",
"END_DATE": "End date",
"ID": "ID",
"DESCRIPTION": "Description",
"DESCRIPTION_DEFAULT": "No description",
"FORM_NAME": "Form Name",
"FORM_NAME_DEFAULT": "No form"
}
}
}

View File

@@ -18,4 +18,6 @@
export * from './task-list/public-api';
export * from './task-filters/public-api';
export * from './start-task/public-api';
export * from './task-header/public-api';
export * from './task-cloud.module';

View File

@@ -42,6 +42,9 @@ export class TaskDetailsCloudModel {
serviceName: string;
serviceFullName: string;
serviceVersion: string;
managerOfCandidateGroup: boolean;
memberOfCandidateGroup: boolean;
memberOfCandidateUsers: boolean;
constructor(obj?: any) {
if (obj) {
@@ -70,9 +73,12 @@ export class TaskDetailsCloudModel {
this.serviceName = obj.serviceName || null;
this.serviceFullName = obj.serviceFullName || null;
this.serviceVersion = obj.serviceVersion || null;
this.managerOfCandidateGroup = obj.managerOfCandidateGroup || null;
this.memberOfCandidateGroup = obj.memberOfCandidateGroup || null;
this.memberOfCandidateUsers = obj.memberOfCandidateUsers || null;
}
}
}
}
export interface StartTaskCloudResponseModel {
entry: TaskDetailsCloudModel;

View File

@@ -19,17 +19,20 @@ import { NgModule } from '@angular/core';
import { TaskListCloudModule } from './task-list/task-list-cloud.module';
import { TaskFiltersCloudModule } from './task-filters/task-filters-cloud.module';
import { StartTaskCloudModule } from './start-task/start-task-cloud.module';
import { TaskHeaderCloudModule } from './task-header/task-header-cloud.module';
@NgModule({
imports: [
TaskListCloudModule,
TaskFiltersCloudModule,
StartTaskCloudModule
StartTaskCloudModule,
TaskHeaderCloudModule
],
exports: [
TaskListCloudModule,
TaskFiltersCloudModule,
StartTaskCloudModule
StartTaskCloudModule,
TaskHeaderCloudModule
]
})
export class TaskCloudModule { }

View File

@@ -0,0 +1,15 @@
<mat-card *ngIf="isTaskValid()" class="adf-card-container">
<mat-card-content>
<adf-card-view
[properties]="properties"
[editable]="!isCompleted()">
</adf-card-view>
</mat-card-content>
<mat-card-actions class="adf-controls">
<button *ngIf="isTaskClaimedByCandidateMember()" mat-button data-automation-id="header-unclaim-button" id="unclaim-task" (click)="unclaimTask()" class="adf-claim-controls">{{ 'ADF_CLOUD_TASK_HEADER.BUTTON.UNCLAIM' | translate }}
</button>
<button *ngIf="isTaskClaimable()" mat-button data-automation-id="header-claim-button" id="claim-task" (click)="claimTask()" class="adf-claim-controls">{{ 'ADF_CLOUD_TASK_HEADER.BUTTON.CLAIM' | translate }}
</button>
</mat-card-actions>
</mat-card>

View File

@@ -0,0 +1,40 @@
@mixin adf-task-list-header-theme($theme) {
$primary: map-get($theme, primary);
.adf {
&-controls {
display: flex;
justify-content: space-between;
}
&-edit-controls {
display: flex;
justify-content: flex-end;
margin-left: auto;
}
&-switch-to-edit-mode,
&-save-edit-mode {
color: mat-color($primary);
}
&-cancel-edit-mode,
&-claim-controls {
color: rgb(131, 131, 131);
}
&-card-container {
font-family: inherit;
}
}
@media screen and ($mat-small) {
adf-card-view .adf-property-value {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}

View File

@@ -0,0 +1,155 @@
/*!
* @license
* Copyright 2016 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed, AppConfigService } from '@alfresco/adf-core';
import { TaskHeaderCloudComponent } from './task-header-cloud.component';
import { taskDetailsCloudMock } from '../mocks/task-details-cloud.mock';
import { TaskHeaderCloudModule } from '../task-header-cloud.module';
import { By } from '@angular/platform-browser';
import { TaskHeaderCloudService } from '../services/task-header-cloud.service';
import { of } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
describe('TaskHeaderComponent', () => {
let component: TaskHeaderCloudComponent;
let fixture: ComponentFixture<TaskHeaderCloudComponent>;
let service: TaskHeaderCloudService;
let appConfigService: AppConfigService;
setupTestBed({
imports: [
ProcessServiceCloudTestingModule,
TaskHeaderCloudModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskHeaderCloudComponent);
component = fixture.componentInstance;
component.appName = 'myApp';
component.taskId = taskDetailsCloudMock.id;
service = TestBed.get(TaskHeaderCloudService);
appConfigService = TestBed.get(AppConfigService);
spyOn(service, 'getTaskById').and.returnValue(of(taskDetailsCloudMock));
});
it('should render empty component if no task details provided', async(() => {
component.appName = undefined;
component.taskId = undefined;
fixture.detectChanges();
expect(fixture.debugElement.children.length).toBe(0);
}));
it('should display assignee', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-assignee"] span'));
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
});
}));
it('should display placeholder if no assignee', async(() => {
component.ngOnInit();
component.taskDetails.assignee = null;
fixture.detectChanges();
fixture.whenStable().then(() => {
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-assignee"] span'));
expect(valueEl.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.ASSIGNEE_DEFAULT');
});
}));
it('should display priority', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-priority"]'));
expect(formNameEl.nativeElement.innerText).toBe('5');
});
}));
it('should display due date', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('Dec 18 2018');
});
}));
it('should display placeholder if no due date', async(() => {
component.ngOnInit();
component.taskDetails.dueDate = null;
fixture.detectChanges();
fixture.whenStable().then(() => {
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE_DEFAULT');
});
}));
it('should display the default parent value if is undefined', async(() => {
component.ngOnInit();
component.taskDetails.processInstanceId = null;
fixture.detectChanges();
fixture.whenStable().then(() => {
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-parentName"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_HEADER.PROPERTIES.PARENT_NAME_DEFAULT');
});
}));
describe('Config Filtering', () => {
it('should show only the properties from the configuration file', async(() => {
spyOn(appConfigService, 'get').and.returnValue(['assignee', 'status']);
component.ngOnInit();
fixture.detectChanges();
let propertyList = fixture.debugElement.queryAll(By.css('.adf-property-list .adf-property'));
fixture.whenStable().then(() => {
expect(propertyList).toBeDefined();
expect(propertyList).not.toBeNull();
expect(propertyList.length).toBe(2);
expect(propertyList[0].nativeElement.textContent).toContain('ADF_CLOUD_TASK_HEADER.PROPERTIES.ASSIGNEE');
expect(propertyList[1].nativeElement.textContent).toContain('ADF_CLOUD_TASK_HEADER.PROPERTIES.STATUS');
});
}));
it('should show all the default properties if there is no configuration', async(() => {
spyOn(appConfigService, 'get').and.returnValue(null);
component.ngOnInit();
fixture.detectChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
let propertyList = fixture.debugElement.queryAll(By.css('.adf-property-list .adf-property'));
expect(propertyList).toBeDefined();
expect(propertyList).not.toBeNull();
expect(propertyList.length).toBe(component.properties.length);
expect(propertyList[0].nativeElement.textContent).toContain('ADF_CLOUD_TASK_HEADER.PROPERTIES.ASSIGNEE');
expect(propertyList[1].nativeElement.textContent).toContain('ADF_CLOUD_TASK_HEADER.PROPERTIES.STATUS');
});
}));
});
});

View File

@@ -0,0 +1,273 @@
/*!
* @license
* Copyright 2016 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, Input, OnInit, EventEmitter, Output } from '@angular/core';
import {
CardViewDateItemModel,
CardViewItem,
CardViewTextItemModel,
CardViewBaseItemModel,
TranslationService,
AppConfigService,
CardViewMapItemModel,
UpdateNotification,
CardViewUpdateService,
StorageService
} from '@alfresco/adf-core';
import { TaskHeaderCloudService } from '../services/task-header-cloud.service';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
@Component({
selector: 'adf-cloud-task-header',
templateUrl: './task-header-cloud.component.html',
styleUrls: ['./task-header-cloud.component.scss']
})
export class TaskHeaderCloudComponent implements OnInit {
/** (Required) The appName */
@Input()
appName: string;
/** (Required) The id of the task. */
@Input()
taskId: string;
/** The id of the task. */
@Input()
readOnly: boolean = false;
/** Emitted when the task is claimed. */
@Output()
claim: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the task is unclaimed (ie, requeued). */
@Output()
unclaim: EventEmitter<any> = new EventEmitter<any>();
taskDetails: TaskDetailsCloudModel = new TaskDetailsCloudModel();
properties: CardViewItem [];
inEdit: boolean = false;
private currentUser: string;
constructor(private taskHeaderCloudService: TaskHeaderCloudService,
private translationService: TranslationService,
private appConfig: AppConfigService,
private cardViewUpdateService: CardViewUpdateService,
private storage: StorageService) {
}
ngOnInit() {
this.loadCurrentBpmUserId();
if (this.appName && this.taskId) {
this.loadTaskDetailsById(this.appName, this.taskId);
}
this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this));
}
loadCurrentBpmUserId(): any {
this.currentUser = this.storage.getItem('USERNAME');
}
loadTaskDetailsById(appName: string, taskId: string): any {
this.taskHeaderCloudService.getTaskById(appName, taskId).subscribe(
(taskDetails) => {
this.taskDetails = taskDetails;
this.refreshData();
});
}
private initDefaultProperties(parentInfoMap) {
return [
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.ASSIGNEE',
value: this.taskDetails.assignee,
key: 'assignee',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.ASSIGNEE_DEFAULT'),
icon: 'create'
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.STATUS',
value: this.taskDetails.status,
key: 'status'
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.PRIORITY',
value: this.taskDetails.priority,
key: 'priority',
editable: this.isReadOnlyMode()
}
),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE',
value: this.taskDetails.dueDate,
key: 'dueDate',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE_DEFAULT'),
editable: this.isReadOnlyMode()
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CATEGORY',
value: this.taskDetails.category,
key: 'category',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.CATEGORY_DEFAULT')
}
),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CREATED',
value: this.taskDetails.createdDate,
key: 'created'
}
),
new CardViewMapItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.PARENT_NAME',
value: parentInfoMap,
key: 'parentName',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.PARENT_NAME_DEFAULT'),
clickable: this.isReadOnlyMode()
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.PARENT_TASK_ID',
value: this.taskDetails.parentTaskId,
key: 'parentTaskId'
}
),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.END_DATE',
value: this.taskDetails.claimedDate,
key: 'endDate'
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.ID',
value: this.taskDetails.id,
key: 'id'
}
),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.DESCRIPTION',
value: this.taskDetails.description,
key: 'description',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.DESCRIPTION_DEFAULT'),
multiline: true,
editable: this.isReadOnlyMode()
}
)
];
}
/**
* Refresh the card data
*/
refreshData() {
if (this.taskDetails) {
const defaultProperties = this.initDefaultProperties(this.getParentInfo());
const filteredProperties: string[] = this.appConfig.get('adf-cloud-task-header.presets.properties');
this.properties = defaultProperties.filter((cardItem) => this.isValidSelection(filteredProperties, cardItem));
}
}
/**
* Save a task detail and update it after a successful response
*
* @param updateNotification
*/
private updateTaskDetails(updateNotification: UpdateNotification) {
this.taskHeaderCloudService.updateTask(this.appName, this.taskId, updateNotification.changed)
.subscribe(
(taskDetails) => {
this.taskDetails = taskDetails;
this.refreshData();
}
);
}
getParentInfo() {
if (this.taskDetails.processInstanceId && this.taskDetails.processDefinitionId) {
return new Map([[this.taskDetails.processInstanceId, this.taskDetails.processDefinitionId]]);
}
}
isCompleted() {
return this.taskDetails && this.taskDetails.status === 'completed';
}
isTaskClaimable(): boolean {
return !this.hasAssignee() && this.isCandidateMember();
}
hasAssignee(): boolean {
return !!this.taskDetails.assignee ? true : false;
}
isCandidateMember() {
return this.taskDetails.managerOfCandidateGroup || this.taskDetails.memberOfCandidateGroup || this.taskDetails.memberOfCandidateUsers;
}
isTaskClaimedByCandidateMember(): boolean {
return this.isCandidateMember() && this.isAssignedToCurrentUser() && !this.isCompleted();
}
isAssignedToCurrentUser(): boolean {
return this.hasAssignee() && this.isAssignedTo(this.currentUser);
}
isAssignedTo(userName): boolean {
return this.hasAssignee() ? this.taskDetails.assignee === userName : false;
}
isTaskValid() {
return this.appName && this.taskId;
}
isReadOnlyMode() {
return !this.readOnly;
}
claimTask() {
this.taskHeaderCloudService.claimTask(this.appName, this.taskId, this.currentUser).subscribe(
(res: any) => {
this.loadTaskDetailsById(this.appName, this.taskId);
this.claim.emit(this.taskId);
});
}
unclaimTask() {
this.taskHeaderCloudService.unclaimTask(this.appName, this.taskId).subscribe(
() => {
this.loadTaskDetailsById(this.appName, this.taskId);
this.unclaim.emit(this.taskId);
});
}
private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean {
return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true;
}
}

View File

@@ -0,0 +1,46 @@
/*!
* @license
* Copyright 2016 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.
*/
export const fakeTaskDetailsCloud = {
'entry': {
'serviceName': 'task-app-rb',
'serviceFullName': 'task-app-rb',
'serviceVersion': '',
'appName': 'task-app',
'appVersion': '',
'serviceType': null,
'id': '68d54a8f',
'assignee': 'Phil Woods',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': 1545048055900,
'dueDate': 1545091200000,
'claimedDate': 1545140162601,
'priority': 0,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'ASSIGNED',
'owner': 'Phil Woods',
'parentTaskId': null,
'formKey': null,
'lastModified': 1545140162601,
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standAlone': true
}
};

View File

@@ -0,0 +1,48 @@
/*!
* @license
* Copyright 2016 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 { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
export const taskDetailsCloudMock = new TaskDetailsCloudModel(
{
'serviceName': 'task-app-rb',
'serviceFullName': 'task-app-rb',
'serviceVersion': '',
'appName': 'task-app',
'appVersion': '',
'serviceType': null,
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'Wilbur Adams',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': 1545048055900,
'dueDate': 1545091200000,
'claimedDate': null,
'priority': 5,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'ASSIGNED',
'owner': 'superadminuser',
'parentTaskId': null,
'formKey': null,
'lastModified': 1545048055900,
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standAlone': true
}
);

View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 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.
*/
export * from './components/task-header-cloud.component';
export * from './task-header-cloud.module';

View File

@@ -0,0 +1,245 @@
/*!
* @license
* Copyright 2016 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 { async } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core';
import { fakeTaskDetailsCloud } from '../mocks/fake-task-details-response.mock';
import { TaskHeaderCloudService } from './task-header-cloud.service';
describe('Task Header Cloud Service', () => {
let service: TaskHeaderCloudService;
let alfrescoApiMock: AlfrescoApiServiceMock;
function returnFakeTaskDetailsResults() {
return {
oauth2Auth: {
callCustomApi : () => {
return Promise.resolve(fakeTaskDetailsCloud);
}
}
};
}
setupTestBed({
imports: [
CoreModule.forRoot()
]
});
beforeEach(async(() => {
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() );
service = new TaskHeaderCloudService(alfrescoApiMock,
new AppConfigService(null),
new LogService(new AppConfigService(null)));
}));
it('should return the task details when querying by id', (done) => {
const appName = 'taskp-app';
const taskId = '68d54a8f';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.getTaskById(appName, taskId).subscribe((res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.appName).toBe('task-app');
expect(res.id).toBe('68d54a8f');
done();
});
});
it('should throw error if appName is not defined when querying by id', (done) => {
const appName = null;
const taskId = '68d54a8f';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.getTaskById(appName, taskId).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should throw error if taskId is not defined when querying by id', (done) => {
const appName = 'task-app';
const taskId = null;
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.getTaskById(appName, taskId).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should return the task details when updating a task', (done) => {
const appName = 'taskp-app';
const taskId = '68d54a8f';
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe((res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.appName).toBe('task-app');
expect(res.id).toBe('68d54a8f');
done();
});
});
it('should throw error if appName is not defined when updating a task', (done) => {
const appName = null;
const taskId = '68d54a8f';
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should throw error if taskId is not defined when updating a task', (done) => {
const appName = 'task-app';
const taskId = null;
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should return the task details when updating a task', (done) => {
const appName = 'taskp-app';
const taskId = '68d54a8f';
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe((res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.appName).toBe('task-app');
expect(res.id).toBe('68d54a8f');
done();
});
});
it('should throw error if appName is not defined when querying by id', (done) => {
const appName = null;
const taskId = '68d54a8f';
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should throw error if taskId is not defined updating a task', (done) => {
const appName = 'task-app';
const taskId = null;
const updatePayload = { description: 'New description' };
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.updateTask(appName, taskId, updatePayload).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should return the task details when claiming a task', (done) => {
const appName = 'taskp-app';
const assignee = 'user12';
const taskId = '68d54a8f';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.claimTask(appName, taskId, assignee).subscribe((res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.appName).toBe('task-app');
expect(res.id).toBe('68d54a8f');
done();
});
});
it('should throw error if appName is not defined when claiming a task', (done) => {
const appName = null;
const taskId = '68d54a8f';
const assignee = 'user12';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.claimTask(appName, taskId, assignee).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should throw error if taskId is not defined when claiming a task', (done) => {
const appName = 'task-app';
const taskId = null;
const assignee = 'user12';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.claimTask(appName, taskId, assignee).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should return the task details when unclaiming a task', (done) => {
const appName = 'taskp-app';
const taskId = '68d54a8f';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.unclaimTask(appName, taskId).subscribe((res: any) => {
expect(res).toBeDefined();
expect(res).not.toBeNull();
expect(res.appName).toBe('task-app');
expect(res.id).toBe('68d54a8f');
done();
});
});
it('should throw error if appName is not defined when unclaiming a task', (done) => {
const appName = null;
const taskId = '68d54a8f';
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.unclaimTask(appName, taskId).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
it('should throw error if taskId is not defined when unclaiming a task', (done) => {
const appName = 'task-app';
const taskId = null;
spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnFakeTaskDetailsResults);
service.unclaimTask(appName, taskId).subscribe(
() => { },
(error) => {
expect(error).toBe('AppName/TaskId not configured');
done();
});
});
});

View File

@@ -0,0 +1,134 @@
/*!
* @license
* Copyright 2016 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 { AlfrescoApiService, LogService, AppConfigService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
@Injectable({
providedIn: 'root'
})
export class TaskHeaderCloudService {
contextRoot: string;
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
constructor(private alfrescoApiService: AlfrescoApiService,
private appConfigService: AppConfigService,
private logService: LogService) {
this.contextRoot = this.appConfigService.get('bpmHost', '');
}
getTaskById(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
let queryUrl = `${this.contextRoot}/${appName}-query/v1/tasks/${taskId}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
return throwError('AppName/TaskId not configured');
}
}
updateTask(appName: string, taskId: string, updatePayload: any): any {
if (appName && taskId) {
updatePayload.payloadType = 'UpdateTaskPayload';
let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'PUT',
null, null, null,
null, updatePayload,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
return throwError('AppName/TaskId not configured');
}
}
claimTask(appName: string, taskId: string, assignee: string): any {
if (appName && taskId) {
let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/claim?assignee=${assignee}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
return throwError('AppName/TaskId not configured');
}
}
unclaimTask(appName: string, taskId: string): any {
if (appName && taskId) {
let queryUrl = `${this.contextRoot}/${appName}-rb/v1/tasks/${taskId}/release`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
return throwError('AppName/TaskId not configured');
}
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2016 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 { TaskHeaderCloudModule } from './task-header-cloud.module';
describe('TaskCloudModule', () => {
let taskHeaderCloudModule: TaskHeaderCloudModule;
beforeEach(() => {
taskHeaderCloudModule = new TaskHeaderCloudModule();
});
it('should create an instance', () => {
expect(taskHeaderCloudModule).toBeTruthy();
});
});

View File

@@ -0,0 +1,50 @@
/*!
* @license
* Copyright 2016 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MaterialModule } from '../../material.module';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateLoaderService, DataTableModule, TemplateModule, CardViewModule } from '@alfresco/adf-core';
import { TaskHeaderCloudComponent } from './components/task-header-cloud.component';
import { TaskHeaderCloudService } from './services/task-header-cloud.service';
@NgModule({
imports: [
CommonModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderService
}
}),
MaterialModule,
DataTableModule,
TemplateModule,
CardViewModule
],
declarations: [
TaskHeaderCloudComponent
],
exports: [
TaskHeaderCloudComponent
],
providers: [
TaskHeaderCloudService
]
})
export class TaskHeaderCloudModule { }

View File

@@ -85,10 +85,6 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges
@Input()
status: string = '';
/** Toggles default selection of the first row. */
@Input()
selectFirstRow: boolean = true;
/**
* Define which task id should be selected after reloading. If the task id doesn't
* exist or nothing is passed then the first task will be selected.
@@ -220,9 +216,6 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges
return currentRow.entry.id === taskIdSelected;
});
}
if (!dataRow && this.selectFirstRow) {
dataRow = this.rows[0];
}
if (dataRow) {
dataRow.isSelected = true;
this.currentInstanceId = dataRow.entry.id;

View File

@@ -40,7 +40,7 @@ if [ ! -d "$DIRECTORY" ]; then
fi
if [ ! -f $DIRECTORY/deps.txt ]; then
npm run affected:libs -- $HEAD_SHA_BRANCH "HEAD" > $DIRECTORY/deps.txt
npm run affected:libs -- $HEAD_SHA_BRANCH "HEAD" > $DIRECTORY/deps.txt || ( echo "This PR needs to be rebased"; exit 1 )
fi
cat $DIRECTORY/deps.txt