[ADF-4979] Add onChanges detection for Task Header Cloud component (#5208)

* [ADF-4979] Add onChanges detection for Task Header Cloud component

* [ADF-4979] Revert licenses.txt changes

* [ADF-4979] Documentation added for the taskError Event
This commit is contained in:
arditdomi 2019-11-06 11:07:48 +00:00 committed by Maurizio Vitale
parent 7d36400dbd
commit 3c1097fb84
25 changed files with 380 additions and 238 deletions

View File

@ -94,6 +94,12 @@
"TREE_VIEW": "Tree View",
"ICONS": "Icons",
"PEOPLE_GROUPS_CLOUD": "People/Group Cloud",
"TASK_HEADER_CLOUD": {
"COMPONENT_NAME": "Task Header Cloud",
"APP_NAME_INPUT": "App name",
"TASK_ID_INPUT": "Task id",
"FIND_TASK_BUTTON": "Find Task"
},
"PEOPLE_CLOUD": "People Cloud Component",
"GROUPS_CLOUD": "Groups Cloud Component",
"CONFIRM-DIALOG": "Confirmation Dialog",

View File

@ -71,6 +71,7 @@ import { AppsCloudDemoComponent } from './components/cloud/apps-cloud-demo.compo
import { TasksCloudDemoComponent } from './components/cloud/tasks-cloud-demo.component';
import { ProcessesCloudDemoComponent } from './components/cloud/processes-cloud-demo.component';
import { TaskDetailsCloudDemoComponent } from './components/cloud/task-details-cloud-demo.component';
import { TaskHeaderCloudDemoComponent } from './components/cloud/task-header-cloud-demo.component';
import { CloudViewerComponent } from './components/cloud/cloud-viewer.component';
import { ProcessDetailsCloudDemoComponent } from './components/cloud/process-details-cloud-demo.component';
import { StartTaskCloudDemoComponent } from './components/cloud/start-task-cloud-demo.component';
@ -182,6 +183,7 @@ registerLocaleData(localeSv);
TasksCloudDemoComponent,
ProcessesCloudDemoComponent,
TaskDetailsCloudDemoComponent,
TaskHeaderCloudDemoComponent,
CloudViewerComponent,
ProcessDetailsCloudDemoComponent,
StartTaskCloudDemoComponent,

View File

@ -58,6 +58,7 @@ import { TemplateDemoComponent } from './components/template-list/template-demo.
import { FormCloudDemoComponent } from './components/app-layout/cloud/form-demo/cloud-form-demo.component';
import { ConfirmDialogExampleComponent } from './components/confirm-dialog/confirm-dialog-example.component';
import { DemoErrorComponent } from './components/error/demo-error.component';
import { TaskHeaderCloudDemoComponent } from './components/cloud/task-header-cloud-demo.component';
export const appRoutes: Routes = [
{ path: 'login', loadChildren: 'app/components/login/login.module#AppLoginModule' },
{ path: 'logout', component: LogoutComponent },
@ -177,6 +178,10 @@ export const appRoutes: Routes = [
path: 'people-group-cloud',
component: PeopleGroupCloudDemoComponent
},
{
path: 'task-header-cloud',
component: TaskHeaderCloudDemoComponent
},
{
path: 'community',
loadChildren: 'app/components/cloud/community/community.module#AppCommunityModule'

View File

@ -48,12 +48,15 @@ export class AppLayoutComponent implements OnInit, OnDestroy {
{ href: '/node-selector', icon: 'attachment', title: 'APP_LAYOUT.NODE-SELECTOR' },
{ href: '/sites', icon: 'format_list_bulleted', title: 'APP_LAYOUT.SITES' },
{ href: '/task-list', icon: 'assignment', title: 'APP_LAYOUT.TASK_LIST' },
{ href: '/cloud', icon: 'cloud', title: 'APP_LAYOUT.PROCESS_CLOUD', children: [
{ href: '/cloud/', icon: 'cloud', title: 'APP_LAYOUT.HOME' },
{ href: '/cloud/community', icon: 'cloud', title: 'APP_LAYOUT.COMMUNITY' },
{ href: '/form-cloud', icon: 'poll', title: 'APP_LAYOUT.FORM' },
{ href: '/cloud/people-group-cloud', icon: 'group', title: 'APP_LAYOUT.PEOPLE_GROUPS_CLOUD' }
]},
{
href: '/cloud', icon: 'cloud', title: 'APP_LAYOUT.PROCESS_CLOUD', children: [
{ href: '/cloud/', icon: 'cloud', title: 'APP_LAYOUT.HOME' },
{ href: '/cloud/community', icon: 'cloud', title: 'APP_LAYOUT.COMMUNITY' },
{ href: '/form-cloud', icon: 'poll', title: 'APP_LAYOUT.FORM' },
{ href: '/cloud/people-group-cloud', icon: 'group', title: 'APP_LAYOUT.PEOPLE_GROUPS_CLOUD' },
{ href: '/cloud/task-header-cloud', icon: 'cloud', title: 'APP_LAYOUT.TASK_HEADER_CLOUD.COMPONENT_NAME' }
]
},
{ href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES', children: [
{ href: '/activiti', icon: 'vpn_key', title: 'APP_LAYOUT.APP' },
{ href: '/process-list', icon: 'assignment', title: 'APP_LAYOUT.PROCESS_LIST' },

View File

@ -8,8 +8,6 @@
[taskId]="taskId"
(cancelClick)="goBack()"
(taskCompleted)="onTaskCompleted()"
(taskClaimed)="onTaskClaimed()"
(taskUnclaimed)="onTaskUnclaimed()"
(formContentClicked)="onFormContentClicked($event)"
(formSaved)="onFormSaved()">
</adf-cloud-task-form>

View File

@ -60,14 +60,6 @@ export class TaskDetailsCloudDemoComponent {
this.goBack();
}
onTaskClaimed() {
this.taskHeader.ngOnInit();
}
onTaskUnclaimed() {
this.taskHeader.ngOnInit();
}
onFormContentClicked(resourceClicked: any) {
this.previewService.showResource(resourceClicked.nodeId);
}

View File

@ -0,0 +1,31 @@
<h2>{{ 'APP_LAYOUT.TASK_HEADER_CLOUD.COMPONENT_NAME' | translate }}</h2>
<mat-form-field class="app-appName-input">
{{ 'APP_LAYOUT.TASK_HEADER_CLOUD.APP_NAME_INPUT' | translate }}
<input matInput
[type]="'text'"
[formControl]="appNameFormControl">
</mat-form-field>
<mat-form-field class="app-taskId-input">
{{ 'APP_LAYOUT.TASK_HEADER_CLOUD.TASK_ID_INPUT' | translate }}
<input matInput
[type]="'text'"
[formControl]="taskIdFormControl">
</mat-form-field>
<button mat-button *ngIf="appNameFormControl.value && taskIdFormControl.value"
class="app-find-task-button"
(click)="updateTaskHeader()">
{{ 'APP_LAYOUT.TASK_HEADER_CLOUD.FIND_TASK_BUTTON' | translate }}
</button>
<mat-error *ngIf="errorMessage">{{errorMessage}}</mat-error>
<div>
<adf-cloud-task-header #taskHeader
[appName]="appName"
[taskId]="taskId"
(taskError)="onError($event)">
</adf-cloud-task-header>
</div>

View File

@ -0,0 +1,16 @@
.app {
&-appName-input {
width: 500px;
margin-right: 50px;
}
&-taskId-input {
width: 500px;
}
&-find-task-button {
background-color: #ff9800;
margin-left: 30px;
}
}

View File

@ -0,0 +1,49 @@
/*!
* @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, ViewChild } from '@angular/core';
import { TaskHeaderCloudComponent } from '@alfresco/adf-process-services-cloud';
import { FormControl, Validators } from '@angular/forms';
@Component({
templateUrl: './task-header-cloud-demo.component.html',
styleUrls: ['./task-header-cloud-demo.component.scss']
})
export class TaskHeaderCloudDemoComponent {
@ViewChild('taskHeader')
taskHeader: TaskHeaderCloudComponent;
appName: string;
taskId: string;
errorMessage;
appNameFormControl = new FormControl('', Validators.required);
taskIdFormControl = new FormControl('', Validators.required);
constructor() {}
updateTaskHeader() {
this.errorMessage = undefined;
this.appName = this.appNameFormControl.value;
this.taskId = this.taskIdFormControl.value;
}
onError(error) {
this.errorMessage = error.message;
}
}

View File

@ -35,6 +35,7 @@ Shows all the information related to a task.
| ---- | ---- | ----------- |
| 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). |
| taskError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when there is an error about the requested task |
## Details

View File

@ -50,7 +50,7 @@ export class ProcessHeaderCloudComponent implements OnChanges {
}
ngOnChanges() {
if ((this.appName || this.appName === '') && this.processInstanceId) {
if (this.appName && this.processInstanceId) {
this.loadProcessInstanceDetails(this.appName, this.processInstanceId);
}
}

View File

@ -45,7 +45,7 @@ export class ProcessHeaderCloudService extends BaseCloudService {
* @returns Process instance details
*/
getProcessInstanceById(appName: string, processInstanceId: string): Observable<ProcessInstanceCloud> {
if ((appName || appName === '') && processInstanceId) {
if (appName && processInstanceId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/process-instances/${processInstanceId}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',

View File

@ -185,7 +185,7 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan
reload() {
this.requestNode = this.createRequestNode();
if (this.requestNode.appName || this.requestNode.appName === '') {
if (this.requestNode.appName) {
this.load(this.requestNode);
} else {
this.rows = [];

View File

@ -39,7 +39,7 @@ export class ProcessListCloudService extends BaseCloudService {
* @returns Process information
*/
getProcessByRequest(requestNode: ProcessQueryCloudRequestModel): Observable<any> {
if (requestNode.appName || requestNode.appName === '') {
if (requestNode.appName) {
const queryUrl = this.buildQueryUrl(requestNode);
const queryParams = this.buildQueryParams(requestNode);
const sortingParams = this.buildSortingParam(requestNode.sorting);

View File

@ -48,7 +48,7 @@ export class StartProcessCloudService extends BaseCloudService {
*/
getProcessDefinitions(appName: string): Observable<ProcessDefinitionCloud[]> {
if (appName || appName === '') {
if (appName) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/process-definitions`;
return from(this.alfrescoApiService.getInstance()

View File

@ -41,7 +41,7 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
* @returns List of user preferences
*/
getPreferences(appName: string): Observable<any> {
if (appName || appName === '') {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName);
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'GET',
@ -62,7 +62,7 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
* @returns Observable of user preference
*/
getPreferenceByKey(appName: string, key: string): Observable<any> {
if (appName || appName === '') {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
return from(
this.alfrescoApiService.getInstance()
@ -85,7 +85,7 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
* @returns Observable of created user preferences
*/
createPreference(appName: string, key: string, newPreference: any): Observable<any> {
if (appName || appName === '') {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
const requestPayload = JSON.stringify(newPreference);
return from(this.alfrescoApiService.getInstance()
@ -121,7 +121,7 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
* @returns Observable of delete operation status
*/
deletePreference(appName: string, key: string): Observable<any> {
if (appName || appName === '') {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'DELETE',

View File

@ -68,7 +68,7 @@ export class ClaimTaskDirective implements OnInit {
}
isAppValid(): boolean {
return (this.appName && this.appName.length > 0) || (this.appName === '');
return (this.appName && this.appName.length > 0);
}
@HostListener('click')

View File

@ -65,7 +65,7 @@ export class CompleteTaskDirective implements OnInit {
}
isAppValid(): boolean {
return (this.appName && this.appName.length > 0) || (this.appName === '');
return (this.appName && this.appName.length > 0);
}
@HostListener('click')

View File

@ -66,7 +66,7 @@ export class UnClaimTaskDirective implements OnInit {
}
isAppValid(): boolean {
return (this.appName && this.appName.length > 0) || (this.appName === '');
return (this.appName && this.appName.length > 0);
}
@HostListener('click')

View File

@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { AlfrescoApiService, LogService, AppConfigService, IdentityUserService } from '@alfresco/adf-core';
import { from, throwError, Observable, of } from 'rxjs';
import { from, throwError, Observable, of, Subject } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { TaskDetailsCloudModel, StartTaskCloudResponseModel } from '../start-task/models/task-details-cloud.model';
import { BaseCloudService } from '../../services/base-cloud.service';
@ -32,6 +32,9 @@ export class TaskCloudService extends BaseCloudService {
accepts = ['application/json'];
returnType = Object;
private dataChangesDetected = new Subject();
dataChangesDetected$: Observable<object>;
constructor(
private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
@ -40,6 +43,7 @@ export class TaskCloudService extends BaseCloudService {
) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
this.dataChangesDetected$ = this.dataChangesDetected.asObservable();
}
/**
@ -49,7 +53,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Details of the task that was completed
*/
completeTask(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = this.buildCompleteTaskUrl(appName, taskId);
const bodyParam = { 'payloadType': 'CompleteTaskPayload' };
const pathParams = {}, queryParams = {}, headerParams = {},
@ -116,7 +120,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Details of the claimed task
*/
claimTask(appName: string, taskId: string, assignee: string): Observable<TaskDetailsCloudModel> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/claim?assignee=${assignee}`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
@ -143,7 +147,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Details of the task that was unclaimed
*/
unclaimTask(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/release`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
@ -170,7 +174,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Task details
*/
getTaskById(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
@ -180,6 +184,7 @@ export class TaskCloudService extends BaseCloudService {
this.returnType, null, null)
).pipe(
map((res: any) => {
this.dataChangesDetected.next();
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
@ -224,7 +229,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Updated task details
*/
updateTask(appName: string, taskId: string, updatePayload: any): Observable<TaskDetailsCloudModel> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
updatePayload.payloadType = 'UpdateTaskPayload';
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}`;
@ -253,7 +258,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Candidate users
*/
getCandidateUsers(appName: string, taskId: string): Observable<string[]> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-users`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
@ -280,7 +285,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Candidate groups
*/
getCandidateGroups(appName: string, taskId: string): Observable<string[]> {
if ((appName || appName === '') && taskId) {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-groups`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',

View File

@ -1,4 +1,4 @@
<h3 class="adf-task-title">{{ taskDetails.name }}</h3>
<h3 class="adf-task-title">{{ taskDetails?.name }}</h3>
<div class="adf-task-header-container">
<mat-card *ngIf="isTaskValid()" class="adf-card-container">

View File

@ -21,7 +21,7 @@ import { TaskHeaderCloudComponent } from './task-header-cloud.component';
import { assignedTaskDetailsCloudMock } from '../mocks/task-details-cloud.mock';
import { TaskHeaderCloudModule } from '../task-header-cloud.module';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { RouterTestingModule } from '@angular/router/testing';
import { TaskCloudService } from '../../services/task-cloud.service';
@ -35,7 +35,12 @@ describe('TaskHeaderCloudComponent', () => {
let getCandidateGroupsSpy: jasmine.Spy;
let getCandidateUsersSpy: jasmine.Spy;
const identityUserMock = { username: 'testuser', firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' };
const identityUserMock = {
username: 'testuser',
firstName: 'fake-identity-first-name',
lastName: 'fake-identity-last-name',
email: 'fakeIdentity@email.com'
};
const mockCandidateUsers = ['mockuser1', 'mockuser2', 'mockuser3'];
const mockCandidateGroups = ['mockgroup1', 'mockgroup2', 'mockgroup3'];
@ -48,200 +53,222 @@ describe('TaskHeaderCloudComponent', () => {
providers: [IdentityUserService]
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskHeaderCloudComponent);
component = fixture.componentInstance;
component.appName = 'myApp';
component.taskId = assignedTaskDetailsCloudMock.id;
service = TestBed.get(TaskCloudService);
identityUserService = TestBed.get(IdentityUserService);
appConfigService = TestBed.get(AppConfigService);
spyOn(service, 'getTaskById').and.returnValue(of(assignedTaskDetailsCloudMock));
getCandidateUsersSpy = spyOn(service, 'getCandidateUsers').and.returnValue(of(mockCandidateUsers));
getCandidateGroupsSpy = spyOn(service, 'getCandidateGroups').and.returnValue(of(mockCandidateGroups));
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock);
});
describe('Task Details', () => {
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(2);
}));
it('should display assignee', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-assignee"] span'));
expect(formNameEl.nativeElement.innerText).toBe('AssignedTaskUser');
});
}));
it('should display placeholder if no assignee', async(() => {
component.ngOnInit();
component.taskDetails.assignee = null;
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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');
beforeEach(() => {
fixture = TestBed.createComponent(TaskHeaderCloudComponent);
component = fixture.componentInstance;
component.appName = 'myApp';
component.taskId = assignedTaskDetailsCloudMock.id;
service = TestBed.get(TaskCloudService);
identityUserService = TestBed.get(IdentityUserService);
appConfigService = TestBed.get(AppConfigService);
spyOn(service, 'getTaskById').and.returnValue(of(assignedTaskDetailsCloudMock));
getCandidateUsersSpy = spyOn(service, 'getCandidateUsers').and.returnValue(of(mockCandidateUsers));
getCandidateGroupsSpy = spyOn(service, 'getCandidateGroups').and.returnValue(of(mockCandidateGroups));
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock);
});
}));
it('should display priority', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-priority"]'));
expect(formNameEl.nativeElement.innerText).toBe('5');
});
}));
it('should display error if priority is not a number', async(() => {
component.ngOnInit();
component.taskDetails.assignee = 'testuser';
fixture.detectChanges();
fixture.whenStable().then(() => {
const edit = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-edit-icon-priority"]'));
edit.nativeElement.click();
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(2);
}));
const formPriorityEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-editinput-priority"]'));
formPriorityEl.nativeElement.value = 'stringValue';
formPriorityEl.nativeElement.dispatchEvent(new Event('input'));
it('should display assignee', async(() => {
component.ngOnChanges();
fixture.detectChanges();
const submitEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-update-priority"]'));
submitEl.nativeElement.click();
fixture.detectChanges();
const errorMessageEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-error-priority"]'));
expect(errorMessageEl).not.toBeNull();
});
}));
it('should display due date', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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(() => {
const 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(() => {
const 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');
});
}));
it('should display candidate user', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
const candidateUser1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser1"] span');
const candidateUser2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser2"] span');
expect(getCandidateUsersSpy).toHaveBeenCalled();
expect(candidateUser1.innerText).toBe('mockuser1');
expect(candidateUser2.innerText).toBe('mockuser2');
});
}));
it('should display placeholder if no candidate users', async(() => {
component.ngOnInit();
getCandidateUsersSpy.and.returnValue(of([]));
fixture.detectChanges();
fixture.whenStable().then(() => {
const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateUsers"]'));
const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS');
expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS_DEFAULT');
});
}));
it('should display candidate groups', async(() => {
component.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
const candidateGroup1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup1"] span');
const candidateGroup2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup2"] span');
expect(getCandidateGroupsSpy).toHaveBeenCalled();
expect(candidateGroup1.innerText).toBe('mockgroup1');
expect(candidateGroup2.innerText).toBe('mockgroup2');
});
}));
it('should display placeholder if no candidate groups', async(() => {
component.ngOnInit();
getCandidateGroupsSpy.and.returnValue(of([]));
fixture.detectChanges();
fixture.whenStable().then(() => {
const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateGroups"]'));
const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS');
expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS_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();
const 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');
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-assignee"] span'));
expect(formNameEl.nativeElement.innerText).toBe('AssignedTaskUser');
});
}));
it('should show all the default properties if there is no configuration', async(() => {
spyOn(appConfigService, 'get').and.returnValue(null);
component.ngOnInit();
fixture.detectChanges();
it('should display placeholder if no assignee', async(() => {
component.taskDetails.assignee = null;
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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');
const 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.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-priority"]'));
expect(formNameEl.nativeElement.innerText).toBe('5');
});
}));
it('should display error if priority is not a number', async(() => {
component.ngOnChanges();
component.taskDetails.assignee = 'testuser';
fixture.detectChanges();
fixture.whenStable().then(() => {
const edit = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-edit-icon-priority"]'));
edit.nativeElement.click();
fixture.detectChanges();
const formPriorityEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-editinput-priority"]'));
formPriorityEl.nativeElement.value = 'stringValue';
formPriorityEl.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
const submitEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-update-priority"]'));
submitEl.nativeElement.click();
fixture.detectChanges();
const errorMessageEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-error-priority"]'));
expect(errorMessageEl).not.toBeNull();
});
}));
it('should display due date', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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.taskDetails.dueDate = null;
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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.ngOnChanges();
component.taskDetails.processInstanceId = null;
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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');
});
}));
it('should display candidate user', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const candidateUser1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser1"] span');
const candidateUser2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockuser2"] span');
expect(getCandidateUsersSpy).toHaveBeenCalled();
expect(candidateUser1.innerText).toBe('mockuser1');
expect(candidateUser2.innerText).toBe('mockuser2');
});
}));
it('should display placeholder if no candidate users', async(() => {
getCandidateUsersSpy.and.returnValue(of([]));
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateUsers"]'));
const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS');
expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS_DEFAULT');
});
}));
it('should display candidate groups', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const candidateGroup1 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup1"] span');
const candidateGroup2 = fixture.nativeElement.querySelector('[data-automation-id="card-arrayitem-chip-mockgroup2"] span');
expect(getCandidateGroupsSpy).toHaveBeenCalled();
expect(candidateGroup1.innerText).toBe('mockgroup1');
expect(candidateGroup2.innerText).toBe('mockgroup2');
});
}));
it('should display placeholder if no candidate groups', async(() => {
getCandidateGroupsSpy.and.returnValue(of([]));
component.refreshData();
fixture.detectChanges();
fixture.whenStable().then(() => {
const labelValue = fixture.debugElement.query(By.css('[data-automation-id="card-array-label-candidateGroups"]'));
const defaultElement = fixture.debugElement.query(By.css('[data-automation-id="card-arrayitem-default"]'));
expect(labelValue.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS');
expect(defaultElement.nativeElement.innerText).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS_DEFAULT');
});
}));
describe('Config Filtering', () => {
it('should show only the properties from the configuration file', async(() => {
spyOn(appConfigService, 'get').and.returnValue(['assignee', 'status']);
component.ngOnChanges();
fixture.detectChanges();
const 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.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const 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');
});
}));
});
});
describe('Task Errors', () => {
beforeEach(() => {
fixture = TestBed.createComponent(TaskHeaderCloudComponent);
component = fixture.componentInstance;
service = TestBed.get(TaskCloudService);
spyOn(service, 'getTaskById').and.returnValue(throwError('Task not found error'));
});
it('should emit an error when getTaskById returns an error', async(() => {
const taskErrorSpy = spyOn(component.taskError, 'emit');
component.loadTaskDetailsById(component.appName, component.taskId);
fixture.detectChanges();
expect(taskErrorSpy).toHaveBeenCalledWith('Task not found error');
}));
});
});

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, Input, OnInit, EventEmitter, Output, OnDestroy } from '@angular/core';
import { Component, Input, EventEmitter, Output, OnDestroy, OnChanges, OnInit } from '@angular/core';
import {
CardViewDateItemModel,
CardViewItem,
@ -30,7 +30,7 @@ import {
import { TaskDetailsCloudModel, TaskStatusEnum } from '../../start-task/models/task-details-cloud.model';
import { Router } from '@angular/router';
import { TaskCloudService } from '../../services/task-cloud.service';
import { Subject, Observable } from 'rxjs';
import { Subject } from 'rxjs';
import { NumericFieldValidator } from '../../../validators/numeric-field.validator';
import { takeUntil } from 'rxjs/operators';
@ -39,7 +39,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './task-header-cloud.component.html',
styleUrls: ['./task-header-cloud.component.scss']
})
export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges {
/** (Required) The name of the application. */
@Input()
@ -57,6 +57,10 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
@Output()
unclaim: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when the task has not been found. */
@Output()
taskError: EventEmitter<any> = new EventEmitter<any>();
taskDetails: TaskDetailsCloudModel = new TaskDetailsCloudModel();
properties: CardViewItem[];
inEdit: boolean = false;
@ -79,13 +83,23 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
}
ngOnInit() {
if ((this.appName || this.appName === '') && this.taskId) {
this.taskCloudService.dataChangesDetected$
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => {
this.loadTaskDetailsById(this.appName, this.taskId);
}
});
this.cardViewUpdateService.itemUpdated$
.pipe(takeUntil(this.onDestroy$))
.subscribe(this.updateTaskDetails.bind(this));
.subscribe(this.updateTaskDetails.bind(this)
);
}
ngOnChanges() {
this.taskDetails = new TaskDetailsCloudModel();
if (this.appName && this.taskId) {
this.loadTaskDetailsById(this.appName, this.taskId);
}
}
loadTaskDetailsById(appName: string, taskId: string): any {
@ -97,7 +111,8 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
} else {
this.refreshData();
}
});
},
(err) => this.taskError.emit(err), () => {});
}
private initDefaultProperties() {
@ -202,7 +217,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
new CardViewArrayItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS',
value: this.getCandidateUsers(),
value: this.taskCloudService.getCandidateUsers(this.appName, this.taskId),
key: 'candidateUsers',
icon: 'person',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_USERS_DEFAULT'),
@ -212,7 +227,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
new CardViewArrayItemModel(
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS',
value: this.getCandidateGroups(),
value: this.taskCloudService.getCandidateGroups(this.appName, this.taskId),
key: 'candidateGroups',
icon: 'group',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.CANDIDATE_GROUPS_DEFAULT'),
@ -222,14 +237,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
];
}
private getCandidateUsers(): Observable<string[]> {
return this.taskCloudService.getCandidateUsers(this.appName, this.taskId);
}
private getCandidateGroups(): Observable<string[]> {
return this.taskCloudService.getCandidateGroups(this.appName, this.taskId);
}
/**
* Refresh the card data
*/
@ -275,7 +282,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
}
isTaskValid(): boolean {
return (this.appName || this.appName === '') && !!this.taskId;
return (this.appName) && !!this.taskId;
}
isTaskAssigned(): boolean {

View File

@ -233,7 +233,7 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges
reload() {
this.requestNode = this.createRequestNode();
if (this.requestNode.appName || this.requestNode.appName === '') {
if (this.requestNode.appName) {
this.load(this.requestNode);
} else {
this.rows = [];

View File

@ -41,7 +41,7 @@ export class TaskListCloudService extends BaseCloudService {
*/
getTaskByRequest(requestNode: TaskQueryCloudRequestModel): Observable<any> {
if (requestNode.appName || requestNode.appName === '') {
if (requestNode.appName) {
const queryUrl = this.buildQueryUrl(requestNode);
const queryParams = this.buildQueryParams(requestNode);
const sortingParams = this.buildSortingParam(requestNode.sorting);