[ADF-4123] Process Cloud Instance Details Header component (#4418)

* [ADF-4123] Added service to get process instance details

* [ADF-4123] Added process header component

* [ADF-4123] Added more properties to model

* [ADF-4123] Exported process sidebar

* [ADF-4123] Added new process instance details demo component

* [ADF-4123] Added tests for module

* [ADF-4123] Added tests for process-header

* [ADF-4123] Added documentation
This commit is contained in:
Deepak Paul
2019-03-13 17:03:40 +05:30
committed by Eugenio Romano
parent 0b218f0978
commit 574cc9c1a6
21 changed files with 710 additions and 3 deletions

View File

@@ -204,5 +204,20 @@
"FORM_VALIDATION": {
"INVALID_FIELD": "Enter a different value"
}
},
"ADF_CLOUD_PROCESS_HEADER": {
"PROPERTIES": {
"ID": "ID",
"NAME": "Name",
"DESCRIPTION": "Description",
"DESCRIPTION_DEFAULT": "No description",
"STATUS": "Status",
"BUSINESS_KEY": "Business Key",
"INITIATOR": "Initiator",
"START_DATE": "Start Date",
"LAST_MODIFIED": "Last Modified",
"PARENT_ID": "Parent Id",
"NONE": "None"
}
}
}

View File

@@ -20,18 +20,21 @@ import { ProcessFiltersCloudModule } from './process-filters/process-filters-clo
import { ProcessListCloudModule } from './process-list/process-list-cloud.module';
import { StartProcessCloudModule } from './start-process/start-process-cloud.module';
import { CoreModule } from '@alfresco/adf-core';
import { ProcessHeaderCloudModule } from './process-header/public-api';
@NgModule({
imports: [
ProcessFiltersCloudModule,
ProcessListCloudModule,
StartProcessCloudModule,
ProcessHeaderCloudModule,
CoreModule
],
exports: [
ProcessFiltersCloudModule,
ProcessListCloudModule,
StartProcessCloudModule
StartProcessCloudModule,
ProcessHeaderCloudModule
]
})
export class ProcessCloudModule { }

View File

@@ -0,0 +1,5 @@
<mat-card *ngIf="processInstanceDetails">
<mat-card-content>
<adf-card-view [properties]="properties"></adf-card-view>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,230 @@
/*!
* @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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed, AppConfigService } from '@alfresco/adf-core';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { ProcessHeaderCloudComponent } from './process-header-cloud.component';
import { ProcessHeaderCloudModule } from '../process-header-cloud.module';
import { ProcessHeaderCloudService } from '../services/process-header-cloud.service';
const processInstanceDetailsCloudMock = {
appName: 'app-form-mau',
businessKey: 'MyBusinessKey',
description: 'new desc',
id: '00fcc4ab-4290-11e9-b133-0a586460016a',
initiator: 'devopsuser',
lastModified: 1552152187081,
name: 'new name',
parentId: '00fcc4ab-4290-11e9-b133-0a586460016b',
startDate: 1552152187080,
status: 'RUNNING'
};
describe('ProcessHeaderCloudComponent', () => {
let component: ProcessHeaderCloudComponent;
let fixture: ComponentFixture<ProcessHeaderCloudComponent>;
let service: ProcessHeaderCloudService;
let appConfigService: AppConfigService;
setupTestBed({
imports: [
ProcessServiceCloudTestingModule,
ProcessHeaderCloudModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ProcessHeaderCloudComponent);
component = fixture.componentInstance;
service = TestBed.get(ProcessHeaderCloudService);
appConfigService = TestBed.get(AppConfigService);
spyOn(service, 'getProcessInstanceById').and.returnValue(of(processInstanceDetailsCloudMock));
component.appName = 'myApp';
component.processInstanceId = 'sdfsdf-323';
});
it('should render empty component if no process instance details are provided', async(() => {
component.appName = undefined;
component.processInstanceId = undefined;
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.properties).toBeUndefined();
});
}));
it('should display process instance id', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-id"] span'));
expect(formNameEl.nativeElement.innerText).toBe('00fcc4ab-4290-11e9-b133-0a586460016a');
});
}));
it('should display name', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-name"] span'));
expect(formNameEl.nativeElement.innerText).toBe('new name');
});
}));
it('should display description', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-description"] span'));
expect(formNameEl.nativeElement.innerText).toBe('new desc');
});
}));
it('should display placeholder if no description is avilable', async(() => {
processInstanceDetailsCloudMock.description = null;
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-description"] span'));
expect(valueEl.nativeElement.innerText).toBe('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.DESCRIPTION_DEFAULT');
});
}));
it('should display status', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-status"] span'));
expect(formNameEl.nativeElement.innerText).toBe('RUNNING');
});
}));
it('should display initiator', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-initiator"] span'));
expect(formNameEl.nativeElement.innerText).toBe('devopsuser');
});
}));
it('should display start date', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-startDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019');
});
}));
it('should display lastModified date', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-lastModified"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019');
});
}));
it('should display parentId', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-parentId"] span'));
expect(formNameEl.nativeElement.innerText).toBe('00fcc4ab-4290-11e9-b133-0a586460016b');
});
}));
it('should display default value when parentId is not available', async(() => {
processInstanceDetailsCloudMock.parentId = null;
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-parentId"] span'));
expect(formNameEl.nativeElement.innerText).toBe('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NONE');
});
}));
it('should display businessKey', async(() => {
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-businessKey"] span'));
expect(formNameEl.nativeElement.innerText).toBe('MyBusinessKey');
});
}));
it('should display default value when businessKey is not available', async(() => {
processInstanceDetailsCloudMock.businessKey = null;
component.ngOnChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
const formNameEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-businessKey"] span'));
expect(formNameEl.nativeElement.innerText).toBe('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NONE');
});
}));
describe('Config Filtering', () => {
it('should show only the properties from the configuration file', async(() => {
spyOn(appConfigService, 'get').and.returnValue(['name', 'status']);
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(2);
expect(propertyList[0].nativeElement.textContent).toContain('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NAME');
expect(propertyList[1].nativeElement.textContent).toContain('ADF_CLOUD_PROCESS_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_PROCESS_HEADER.PROPERTIES.ID');
expect(propertyList[1].nativeElement.textContent).toContain('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NAME');
});
}));
});
});

View File

@@ -0,0 +1,142 @@
/*!
* @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, Input, OnChanges } from '@angular/core';
import { CardViewItem, CardViewTextItemModel, TranslationService, AppConfigService, CardViewDateItemModel, CardViewBaseItemModel } from '@alfresco/adf-core';
import { ProcessInstanceCloud } from '../../start-process/public-api';
import { ProcessHeaderCloudService } from '../services/process-header-cloud.service';
@Component({
selector: 'adf-cloud-process-header',
templateUrl: './process-header-cloud.component.html',
styleUrls: ['./process-header-cloud.component.scss']
})
export class ProcessHeaderCloudComponent implements OnChanges {
/** (Required) The name of the application. */
@Input()
appName: string;
/** (Required) The id of the process instance. */
@Input()
processInstanceId: string;
processInstanceDetails: ProcessInstanceCloud = new ProcessInstanceCloud();
properties: CardViewItem[];
constructor(
private processHeaderCloudService: ProcessHeaderCloudService,
private translationService: TranslationService,
private appConfig: AppConfigService) {
}
ngOnChanges() {
if (this.appName && this.processInstanceId) {
this.loadProcessInstanceDetails(this.appName, this.processInstanceId);
}
}
private loadProcessInstanceDetails(appName: string, processInstanceId: string) {
this.processHeaderCloudService.getProcessInstanceById(appName, processInstanceId).subscribe(
(processInstanceDetails) => {
this.processInstanceDetails = processInstanceDetails;
this.refreshData();
});
}
/**
* Refresh the card data
*/
refreshData() {
if (this.processInstanceDetails) {
const defaultProperties = this.initDefaultProperties();
const filteredProperties: string[] = this.appConfig.get('adf-cloud-process-header.presets.properties');
this.properties = defaultProperties.filter((cardItem) => this.isValidSelection(filteredProperties, cardItem));
}
}
private initDefaultProperties(): any[] {
return [
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.ID',
value: this.processInstanceDetails.id,
key: 'id'
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NAME',
value: this.processInstanceDetails.name,
key: 'name'
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.DESCRIPTION',
value: this.processInstanceDetails.description,
key: 'description',
default: this.translationService.instant('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.DESCRIPTION_DEFAULT')
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.STATUS',
value: this.processInstanceDetails.status,
key: 'status'
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.INITIATOR',
value: this.processInstanceDetails.initiator,
key: 'initiator'
}),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.START_DATE',
value: this.processInstanceDetails.startDate,
format: 'DD-MM-YYYY',
key: 'startDate'
}),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.LAST_MODIFIED',
value: this.processInstanceDetails.lastModified,
format: 'DD-MM-YYYY',
key: 'lastModified'
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.PARENT_ID',
value: this.processInstanceDetails.parentId,
key: 'parentId',
default: this.translationService.instant('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NONE')
}),
new CardViewTextItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.BUSINESS_KEY',
value: this.processInstanceDetails.businessKey,
key: 'businessKey',
default: this.translationService.instant('ADF_CLOUD_PROCESS_HEADER.PROPERTIES.NONE')
})
];
}
private isValidSelection(filteredProperties: string[], cardItem: CardViewBaseItemModel): boolean {
return filteredProperties ? filteredProperties.indexOf(cardItem.key) >= 0 : true;
}
}

View File

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

View File

@@ -0,0 +1,38 @@
/*!
* @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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MaterialModule } from '../../material.module';
import { DataTableModule, TemplateModule, CoreModule } from '@alfresco/adf-core';
import { ProcessHeaderCloudService } from '../process-header/services/process-header-cloud.service';
import { ProcessHeaderCloudComponent } from './components/process-header-cloud.component';
@NgModule({
imports: [
CommonModule,
MaterialModule,
DataTableModule,
TemplateModule,
CoreModule
],
declarations: [ProcessHeaderCloudComponent],
exports: [ProcessHeaderCloudComponent],
providers: [ProcessHeaderCloudService]
})
export class ProcessHeaderCloudModule { }

View File

@@ -0,0 +1,20 @@
/*!
* @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.
*/
export * from './process-header-cloud.module';
export * from './components/process-header-cloud.component';
export * from './services/process-header-cloud.service';

View File

@@ -0,0 +1,71 @@
/*!
* @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 { 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 { ProcessInstanceCloud } from '../../start-process/public-api';
@Injectable({
providedIn: 'root'
})
export class ProcessHeaderCloudService {
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', '');
}
/**
* Gets details of a process instance.
* @param appName Name of the app
* @param processInstanceId ID of the process instance whose details you want
* @returns Process instance details
*/
getProcessInstanceById(appName: string, processInstanceId: string): Observable<ProcessInstanceCloud> {
if (appName && processInstanceId) {
let queryUrl = `${this.contextRoot}/${appName}-query/v1/process-instances/${processInstanceId}`;
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 ProcessInstanceCloud(res.entry);
}),
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and ProcessInstanceId are mandatory for querying a task');
return throwError('AppName/ProcessInstanceId not configured');
}
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

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

View File

@@ -19,9 +19,13 @@ export class ProcessInstanceCloud {
appName: string;
id: string;
name: string;
description: string;
startDate: Date;
initiator: string;
status: string;
businessKey: string;
lastModified: Date;
parentId: string;
processDefinitionId: string;
processDefinitionKey: string;
@@ -29,9 +33,13 @@ export class ProcessInstanceCloud {
this.appName = obj && obj.appName || null;
this.id = obj && obj.id || null;
this.name = obj && obj.name || null;
this.description = obj && obj.description || null;
this.startDate = obj && obj.startDate || null;
this.initiator = obj && obj.initiator || null;
this.status = obj && obj.status || null;
this.businessKey = obj && obj.businessKey || null;
this.lastModified = obj && obj.lastModified || null;
this.parentId = obj && obj.parentId || null;
this.processDefinitionId = obj && obj.processDefinitionId || null;
this.processDefinitionKey = obj && obj.processDefinitionKey || null;
}