[DW-1486] form versioning and cloud service enhancements (#5248)

* support form versions retrieval

* rework cloud services, reduce code duplication

* revert app config changes

* fix api spelling error

* support task form versioning

* turn TaskDetailsCloudModel into interface

* remove commented code

* fix test

* update tests

* remove useless provider declarations
This commit is contained in:
Denys Vuika
2019-11-14 14:04:21 +00:00
committed by Eugenio Romano
parent 1f768eb22f
commit 3644ba8b7c
40 changed files with 502 additions and 824 deletions

View File

@@ -44,7 +44,7 @@ describe('FormCloudComponent', () => {
visibilityService = new WidgetVisibilityService(null, logService);
spyOn(visibilityService, 'refreshVisibility').and.stub();
spyOn(formRenderingService, 'setComponentTypeResolver').and.returnValue(true);
formCloudService = new FormCloudService(null, new AppConfigService(null), logService);
formCloudService = new FormCloudService(null, new AppConfigService(null));
formService = new FormService(null, null, logService);
formComponent = new FormCloudComponent(formCloudService, formService, null, formRenderingService, visibilityService);
@@ -206,9 +206,10 @@ describe('FormCloudComponent', () => {
formComponent.appName = appName;
formComponent.formId = formId;
formComponent.appVersion = 1;
formComponent.loadForm();
expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId);
expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId, 1);
});
it('should refresh visibility when the form is loaded', () => {
@@ -218,9 +219,10 @@ describe('FormCloudComponent', () => {
formComponent.appName = appName;
formComponent.formId = formId;
formComponent.appVersion = 1;
formComponent.loadForm();
expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId);
expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId, 1);
expect(visibilityService.refreshVisibility).toHaveBeenCalled();
});
@@ -230,10 +232,11 @@ describe('FormCloudComponent', () => {
const appName = 'test-app';
formComponent.appName = appName;
formComponent.appVersion = 1;
const change = new SimpleChange(null, taskId, true);
formComponent.ngOnChanges({ 'taskId': change });
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(appName, taskId);
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(appName, taskId, 1);
});
it('should call the process storage to retrieve the folder with only the taskId', fakeAsync(() => {
@@ -281,10 +284,11 @@ describe('FormCloudComponent', () => {
const appName = 'test-app';
formComponent.appName = appName;
formComponent.appVersion = 1;
const change = new SimpleChange(null, formId, true);
formComponent.ngOnChanges({ 'formId': change });
expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId);
expect(formComponent.getFormById).toHaveBeenCalledWith(appName, formId, 1);
});
it('should not get form on load', () => {
@@ -432,7 +436,7 @@ describe('FormCloudComponent', () => {
spyOn(formCloudService, 'getTaskForm').and.returnValue(of({ taskId: taskId, selectedOutcome: 'custom-outcome' }));
formComponent.formLoaded.subscribe(() => {
expect(formCloudService.getTaskForm).toHaveBeenCalledWith(appName, taskId);
expect(formCloudService.getTaskForm).toHaveBeenCalledWith(appName, taskId, 1);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.taskId).toBe(taskId);
done();
@@ -440,6 +444,7 @@ describe('FormCloudComponent', () => {
formComponent.appName = appName;
formComponent.taskId = taskId;
formComponent.appVersion = 1;
formComponent.loadForm();
});

View File

@@ -53,6 +53,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
@Input()
appName: string;
@Input()
appVersion?: number;
/** Task id to fetch corresponding form and values. */
@Input()
formId: string;
@@ -122,24 +125,25 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
ngOnChanges(changes: SimpleChanges) {
const appName = changes['appName'];
if (appName && appName.currentValue) {
if (this.taskId) {
this.getFormDefinitionWithFolderTask(this.appName, this.taskId, this.processInstanceId);
} else if (this.formId) {
this.getFormById(appName.currentValue, this.formId);
this.getFormById(appName.currentValue, this.formId, this.appVersion);
}
return;
}
const formId = changes['formId'];
if (formId && formId.currentValue && this.appName) {
this.getFormById(this.appName, formId.currentValue);
this.getFormById(this.appName, formId.currentValue, this.appVersion);
return;
}
const taskId = changes['taskId'];
if (taskId && taskId.currentValue && this.appName) {
this.getFormByTaskId(this.appName, taskId.currentValue);
this.getFormByTaskId(this.appName, taskId.currentValue, this.appVersion);
return;
}
@@ -159,9 +163,9 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
loadForm() {
if (this.appName && this.taskId) {
this.getFormByTaskId(this.appName, this.taskId);
this.getFormByTaskId(this.appName, this.taskId, this.appVersion);
} else if (this.appName && this.formId) {
this.getFormById(this.appName, this.formId);
this.getFormById(this.appName, this.formId, this.appVersion);
}
}
@@ -182,35 +186,38 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
}
getFormByTaskId(appName: string, taskId: string): Promise<FormModel> {
getFormByTaskId(appName: string, taskId: string, version?: number): Promise<FormModel> {
return new Promise<FormModel>(resolve => {
forkJoin(this.formCloudService.getTaskForm(appName, taskId),
this.formCloudService.getTaskVariables(appName, taskId))
.pipe(takeUntil(this.onDestroy$))
.subscribe(
(data) => {
this.formCloudRepresentationJSON = data[0];
this.formCloudRepresentationJSON.processVariables = data[1];
this.data = data[1];
const parsedForm = this.parseForm(this.formCloudRepresentationJSON);
this.visibilityService.refreshVisibility(<any> parsedForm);
parsedForm.validateForm();
this.form = parsedForm;
this.onFormLoaded(this.form);
resolve(this.form);
},
(error) => {
this.handleError(error);
// reject(error);
resolve(null);
}
);
forkJoin(
this.formCloudService.getTaskForm(appName, taskId, version),
this.formCloudService.getTaskVariables(appName, taskId)
)
.pipe(takeUntil(this.onDestroy$))
.subscribe(
(data) => {
this.formCloudRepresentationJSON = data[0];
this.formCloudRepresentationJSON.processVariables = data[1];
this.data = data[1];
const parsedForm = this.parseForm(this.formCloudRepresentationJSON);
this.visibilityService.refreshVisibility(<any> parsedForm);
parsedForm.validateForm();
this.form = parsedForm;
this.onFormLoaded(this.form);
resolve(this.form);
},
(error) => {
this.handleError(error);
resolve(null);
}
);
});
}
getFormById(appName: string, formId: string) {
getFormById(appName: string, formId: string, appVersion?: number) {
this.formCloudService
.getForm(appName, formId)
.getForm(appName, formId, appVersion)
.pipe(
map((form: any) => {
const flattenForm = {...form.formRepresentation, ...form.formRepresentation.formDefinition};
@@ -238,7 +245,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
async getFormDefinitionWithFolder(appName: string, taskId: string, processInstanceId: string) {
try {
await this.getFormByTaskId(appName, taskId);
await this.getFormByTaskId(appName, taskId, this.appVersion);
const hasUploadWidget = (<any> this.form).hasUpload;
if (hasUploadWidget) {

View File

@@ -16,7 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AlfrescoApiService, AppConfigService, LogService, setupTestBed, StorageService, UserPreferencesService } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { StartTaskCloudTestingModule } from '../../task/start-task/testing/start-task-cloud.testing.module';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@@ -34,7 +34,6 @@ describe('FormDefinitionCloudComponent', () => {
setupTestBed({
imports: [ProcessServiceCloudTestingModule, StartTaskCloudTestingModule],
providers: [FormDefinitionSelectorCloudService, AlfrescoApiService, AppConfigService, LogService, StorageService, UserPreferencesService],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});

View File

@@ -20,5 +20,5 @@ export interface FormDefinitionSelectorCloudModel {
name?: string;
description?: string;
version?: string;
standAlone?: string;
standalone?: string;
}

View File

@@ -18,7 +18,7 @@
import { TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { FormCloudService } from './form-cloud.service';
import { AlfrescoApiService, CoreModule, setupTestBed, AppConfigService } from '@alfresco/adf-core';
import { AlfrescoApiService, CoreModule, setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
declare let jasmine: any;
@@ -42,8 +42,7 @@ describe('Form Cloud service', () => {
imports: [
NoopAnimationsModule,
CoreModule.forRoot()
],
providers: [FormCloudService, AlfrescoApiService, AppConfigService]
]
});
beforeEach(() => {

View File

@@ -16,11 +16,11 @@
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, LogService, FormValues, AppConfigService, FormOutcomeModel, FormFieldOption, FormModel } from '@alfresco/adf-core';
import { throwError, Observable, from } from 'rxjs';
import { catchError, map, switchMap } from 'rxjs/operators';
import { AlfrescoApiService, FormValues, AppConfigService, FormOutcomeModel, FormFieldOption, FormModel } from '@alfresco/adf-core';
import { Observable, from } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { TaskDetailsCloudModel } from '../../task/start-task/models/task-details-cloud.model';
import { SaveFormRepresentation, CompleteFormRepresentation } from '@alfresco/js-api';
import { CompleteFormRepresentation } from '@alfresco/js-api';
import { TaskVariableCloud, ProcessStorageCloudModel } from '../models/task-variable-cloud.model';
import { BaseCloudService } from '../../services/base-cloud.service';
@@ -29,37 +29,35 @@ import { BaseCloudService } from '../../services/base-cloud.service';
})
export class FormCloudService extends BaseCloudService {
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
constructor(
private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
private logService: LogService
apiService: AlfrescoApiService,
appConfigService: AppConfigService
) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
* Gets the form definition of a task.
* @param appName Name of the app
* @param taskId ID of the target task
* @param version Version of the form
* @returns Form definition
*/
getTaskForm(appName: string, taskId: string): Observable<any> {
getTaskForm(appName: string, taskId: string, version?: number): Observable<any> {
return this.getTask(appName, taskId).pipe(
switchMap((task: TaskDetailsCloudModel) => {
return this.getForm(appName, task.formKey).pipe(
map((form: any) => {
const flattenForm = {...form.formRepresentation, ...form.formRepresentation.formDefinition};
switchMap(task => {
return this.getForm(appName, task.formKey, version).pipe(
map(form => {
const flattenForm = {
...form.formRepresentation,
...form.formRepresentation.formDefinition,
taskId: task.id,
taskName: task.name,
processDefinitionId: task.processDefinitionId,
processInstanceId: task.processInstanceId
};
delete flattenForm.formDefinition;
flattenForm.taskId = task.id;
flattenForm.taskName = task.name;
flattenForm.processDefinitionId = task.processDefinitionId;
flattenForm.processInstanceId = task.processInstanceId;
return flattenForm;
})
);
@@ -72,24 +70,19 @@ export class FormCloudService extends BaseCloudService {
* @param appName Name of the app
* @param taskId ID of the target task
* @param formId ID of the form to save
* @param formValues Form values object
* @param values Form values object
* @returns Updated task details
*/
saveTaskForm(appName: string, taskId: string, processInstanceId: string, formId: string, formValues: FormValues): Observable<TaskDetailsCloudModel> {
const apiUrl = this.buildSaveFormUrl(appName, formId);
const saveFormRepresentation = <SaveFormRepresentation> {values: formValues, taskId: taskId, processInstanceId: processInstanceId};
return from(this.apiService
.getInstance()
.oauth2Auth.callCustomApi(apiUrl, 'POST',
null, null, null,
null, saveFormRepresentation,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
saveTaskForm(appName: string, taskId: string, processInstanceId: string, formId: string, values: FormValues): Observable<TaskDetailsCloudModel> {
const apiUrl = `${this.getBasePath(appName)}/form/v1/forms/${formId}/save`;
const saveFormRepresentation: any = {
values,
taskId,
processInstanceId
};
return this.post(apiUrl, saveFormRepresentation).pipe(
map((res: any) => res.entry)
);
}
@@ -106,10 +99,7 @@ export class FormCloudService extends BaseCloudService {
'',
{overwrite: true}
)).pipe(
map((res: any) => {
return (res.entry);
}),
catchError((err) => this.handleError(err))
map((res: any) => res.entry)
);
}
@@ -123,24 +113,14 @@ export class FormCloudService extends BaseCloudService {
* @returns Updated task details
*/
completeTaskForm(appName: string, taskId: string, processInstanceId: string, formId: string, formValues: FormValues, outcome: string): Observable<TaskDetailsCloudModel> {
const apiUrl = this.buildSubmitFormUrl(appName, formId);
const completeFormRepresentation: any = <CompleteFormRepresentation> {values: formValues, taskId: taskId, processInstanceId: processInstanceId};
const apiUrl = `${this.getBasePath(appName)}/form/v1/forms/${formId}/submit`;
const completeFormRepresentation = <CompleteFormRepresentation> {values: formValues, taskId: taskId, processInstanceId: processInstanceId};
if (outcome) {
completeFormRepresentation.outcome = outcome;
}
return from(this.apiService
.getInstance()
.oauth2Auth.callCustomApi(apiUrl, 'POST',
null, null, null,
null, completeFormRepresentation,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
return this.post(apiUrl, completeFormRepresentation).pipe(
map((res: any) => res.entry)
);
}
@@ -151,36 +131,20 @@ export class FormCloudService extends BaseCloudService {
* @returns Details of the task
*/
getTask(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
const apiUrl = this.buildGetTaskUrl(appName, taskId);
return from(this.apiService
.getInstance()
.oauth2Auth.callCustomApi(apiUrl, '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))
const apiUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}`;
return this.get(apiUrl).pipe(
map((res: any) => res.entry)
);
}
getProcessStorageFolderTask(appName: string, taskId: string, processInstanceId: string): Observable<ProcessStorageCloudModel> {
const apiUrl = this.buildFolderTask(appName, taskId, processInstanceId);
return from(this.apiService
.getInstance()
.oauth2Auth.callCustomApi(apiUrl, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
return this.get(apiUrl).pipe(
map((res: any) => {
return new ProcessStorageCloudModel(res);
}),
catchError((err) => this.handleError(err))
})
);
}
@@ -191,19 +155,12 @@ export class FormCloudService extends BaseCloudService {
* @returns Task variables
*/
getTaskVariables(appName: string, taskId: string): Observable<TaskVariableCloud[]> {
const apiUrl = this.buildGetTaskVariablesUrl(appName, taskId);
return from(this.apiService
.getInstance()
.oauth2Auth.callCustomApi(apiUrl, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
const apiUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/variables`;
return this.get(apiUrl).pipe(
map((res: any) => {
return res.list.entries.map((variable) => new TaskVariableCloud(variable.entry));
}),
catchError((err) => this.handleError(err))
})
);
}
@@ -211,23 +168,17 @@ export class FormCloudService extends BaseCloudService {
* Gets a form definition.
* @param appName Name of the app
* @param formKey key of the target task
* @param version Version of the form
* @returns Form definition
*/
getForm(appName: string, formKey: string): Observable<any> {
const apiUrl = this.buildGetFormUrl(appName, formKey);
const bodyParam = {}, pathParams = {}, queryParams = {}, headerParams = {},
formParams = {};
getForm(appName: string, formKey: string, version?: number): Observable<any> {
let url = `${this.getBasePath(appName)}/form/v1/forms/${formKey}`;
return from(
this.apiService
.getInstance()
.oauth2Auth.callCustomApi(
apiUrl, 'GET', pathParams, queryParams,
headerParams, formParams, bodyParam,
this.contentTypes, this.accepts, this.returnType, null, null)
).pipe(
catchError((err) => this.handleError(err))
);
if (version !== undefined) {
url += `/versions/${version}`;
}
return this.get(url);
}
/**
@@ -236,18 +187,7 @@ export class FormCloudService extends BaseCloudService {
* @returns Array of FormFieldOption object
*/
getDropDownJsonData(url: string): Observable<FormFieldOption[]> {
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(url, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((res: any) => {
return res;
}),
catchError((err) => this.handleError(err))
);
return this.get<FormFieldOption[]>(url);
}
/**
@@ -259,7 +199,10 @@ export class FormCloudService extends BaseCloudService {
*/
parseForm(json: any, data?: TaskVariableCloud[], readOnly: boolean = false): FormModel {
if (json) {
const flattenForm = {...json.formRepresentation, ...json.formRepresentation.formDefinition};
const flattenForm = {
...json.formRepresentation,
...json.formRepresentation.formDefinition
};
delete flattenForm.formDefinition;
const formValues: FormValues = {};
@@ -282,35 +225,9 @@ export class FormCloudService extends BaseCloudService {
return null;
}
private buildGetTaskUrl(appName: string, taskId: string): string {
return `${this.getBasePath(appName)}/query/v1/tasks/${taskId}`;
}
private buildGetFormUrl(appName: string, formKey: string): string {
return `${this.getBasePath(appName)}/form/v1/forms/${formKey}`;
}
private buildSaveFormUrl(appName: string, formId: string): string {
return `${this.getBasePath(appName)}/form/v1/forms/${formId}/save`;
}
private buildSubmitFormUrl(appName: string, formId: string): string {
return `${this.getBasePath(appName)}/form/v1/forms/${formId}/submit`;
}
private buildGetTaskVariablesUrl(appName: string, taskId: string): string {
return `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/variables`;
}
private buildFolderTask(appName: string, taskId: string, processInstanceId: string): string {
return processInstanceId
? `${this.getBasePath(appName)}/process-storage/v1/folders/${processInstanceId}/${taskId}`
: `${this.getBasePath(appName)}/process-storage/v1/folders/${taskId}`;
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -29,7 +29,7 @@ const responseBody = [
name: 'Form 1',
description: '',
version: 0,
standAlone: true
standalone: true
}
},
{
@@ -38,7 +38,7 @@ const responseBody = [
name: 'Form 2',
description: '',
version: 0,
standAlone: false
standalone: false
}
},
{
@@ -87,7 +87,7 @@ describe('Form Definition Selector Cloud Service', () => {
});
});
it('should fetch only standAlone enabled forms when getStandAloneTaskForms is called', (done) => {
it('should fetch only standalone enabled forms when getStandaloneTaskForms is called', (done) => {
oauth2Auth.callCustomApi.and.returnValue(Promise.resolve(responseBody));
service.getStandAloneTaskForms(appName).subscribe((result) => {

View File

@@ -16,10 +16,10 @@
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { catchError, map } from 'rxjs/operators';
import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core';
import { map } from 'rxjs/operators';
import { FormDefinitionSelectorCloudModel } from '../models/form-definition-selector-cloud.model';
import { from, Observable, throwError } from 'rxjs';
import { from, Observable } from 'rxjs';
import { BaseCloudService } from '../../services/base-cloud.service';
@Injectable({
@@ -27,15 +27,10 @@ import { BaseCloudService } from '../../services/base-cloud.service';
})
export class FormDefinitionSelectorCloudService extends BaseCloudService {
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
constructor(private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
private logService: LogService) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
constructor(apiService: AlfrescoApiService,
appConfigService: AppConfigService) {
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -44,24 +39,14 @@ export class FormDefinitionSelectorCloudService extends BaseCloudService {
* @returns Details of the forms
*/
getForms(appName: string): Observable<FormDefinitionSelectorCloudModel[]> {
const url = `${this.getBasePath(appName)}/form/v1/forms`;
const queryUrl = this.buildGetFormsUrl(appName);
const bodyParam = {}, pathParams = {}, queryParams = {}, headerParams = {},
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
return from(
this.apiService
.getInstance()
.oauth2Auth.callCustomApi(
queryUrl, 'GET', pathParams, queryParams,
headerParams, formParams, bodyParam,
contentTypes, accepts, null, null)
).pipe(
return this.get(url).pipe(
map((data: any) => {
return data.map((formData: any) => {
return <FormDefinitionSelectorCloudModel> formData.formRepresentation;
});
}),
catchError((err) => this.handleError(err))
})
);
}
@@ -73,19 +58,8 @@ export class FormDefinitionSelectorCloudService extends BaseCloudService {
getStandAloneTaskForms(appName: string): Observable<FormDefinitionSelectorCloudModel[]> {
return from(this.getForms(appName)).pipe(
map((data: any) => {
return data.filter((formData: any) => formData.standAlone || formData.standAlone === undefined);
}),
catchError((err) => this.handleError(err))
return data.filter((formData: any) => formData.standalone || formData.standalone === undefined);
})
);
}
private buildGetFormsUrl(appName: string): any {
return `${this.getBasePath(appName)}/form/v1/forms`;
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -23,7 +23,6 @@ import { ProcessCloudModule } from './process/process-cloud.module';
import { GroupCloudModule } from './group/group-cloud.module';
import { FormCloudModule } from './form/form-cloud.module';
import { TaskFormModule } from './task/task-form/task-form.module';
import { BaseCloudService } from './services/base-cloud.service';
import { UserPreferenceCloudService } from './services/user-preference-cloud.service';
import {
LocalPreferenceCloudService,
@@ -50,7 +49,6 @@ import {
source: 'assets/adf-process-services-cloud'
}
},
BaseCloudService,
UserPreferenceCloudService,
LocalPreferenceCloudService,
{ provide: PROCESS_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService },

View File

@@ -17,8 +17,8 @@
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 { Observable, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { ProcessInstanceCloud } from '../../start-process/models/process-instance-cloud.model';
import { BaseCloudService } from '../../../services/base-cloud.service';
@@ -26,16 +26,12 @@ import { BaseCloudService } from '../../../services/base-cloud.service';
providedIn: 'root'
})
export class ProcessHeaderCloudService extends BaseCloudService {
contextRoot: string;
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
constructor(private alfrescoApiService: AlfrescoApiService,
private appConfigService: AppConfigService,
constructor(apiService: AlfrescoApiService,
appConfigService: AppConfigService,
private logService: LogService) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -46,27 +42,16 @@ export class ProcessHeaderCloudService extends BaseCloudService {
*/
getProcessInstanceById(appName: string, processInstanceId: string): Observable<ProcessInstanceCloud> {
if (appName && processInstanceId) {
const queryUrl = `${this.getBasePath(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(
const url = `${this.getBasePath(appName)}/query/v1/process-instances/${processInstanceId}`;
return this.get(url).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

@@ -17,20 +17,18 @@
import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
import { Observable, from, throwError } from 'rxjs';
import { Observable, throwError } from 'rxjs';
import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model';
import { BaseCloudService } from '../../../services/base-cloud.service';
@Injectable()
export class ProcessListCloudService extends BaseCloudService {
contentTypes = ['application/json'];
accepts = ['application/json'];
constructor(private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
constructor(apiService: AlfrescoApiService,
appConfigService: AppConfigService,
private logService: LogService) {
super();
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -40,35 +38,27 @@ export class ProcessListCloudService extends BaseCloudService {
*/
getProcessByRequest(requestNode: ProcessQueryCloudRequestModel): Observable<any> {
if (requestNode.appName) {
const queryUrl = this.buildQueryUrl(requestNode);
const queryUrl = `${this.getBasePath(requestNode.appName)}/query/v1/process-instances`;
const queryParams = this.buildQueryParams(requestNode);
const sortingParams = this.buildSortingParam(requestNode.sorting);
if (sortingParams) {
queryParams['sort'] = sortingParams;
}
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, queryParams, null,
null, null, this.contentTypes,
this.accepts, null, null)
);
return this.get(queryUrl, queryParams);
} else {
this.logService.error('Appname is mandatory for querying task');
return throwError('Appname not configured');
}
}
private buildQueryUrl(requestNode: ProcessQueryCloudRequestModel): string {
this.contextRoot = this.appConfigService.get('bpmHost', '');
return `${this.getBasePath(requestNode.appName)}/query/v1/process-instances`;
}
private isPropertyValueValid(requestNode: any, property: string) {
return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined;
}
private buildQueryParams(requestNode: ProcessQueryCloudRequestModel): Object {
const queryParam = {};
for (const property in requestNode) {
if (requestNode.hasOwnProperty(property) &&
!this.isExcludedField(property) &&
@@ -76,6 +66,7 @@ export class ProcessListCloudService extends BaseCloudService {
queryParam[property] = requestNode[property];
}
}
return queryParam;
}

View File

@@ -48,8 +48,15 @@
</form>
<ng-container *ngIf="hasForm() else taskFormCloudButtons">
<adf-cloud-form [appName]="appName" [data]="values" [formId]="processDefinitionCurrent.formKey"
[showSaveButton]="false" [showCompleteButton]="false" [showRefreshButton]="false" [showValidationIcon]="false"
<adf-cloud-form
[appName]="appName"
[appVersion]="processDefinitionCurrent.appVersion"
[data]="values"
[formId]="processDefinitionCurrent.formKey"
[showSaveButton]="false"
[showCompleteButton]="false"
[showRefreshButton]="false"
[showValidationIcon]="false"
[showTitle]="false"
(formLoaded)="onFormLoaded($event)">
<adf-cloud-form-custom-outcomes>

View File

@@ -17,8 +17,8 @@
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { ProcessInstanceCloud } from '../models/process-instance-cloud.model';
import { ProcessPayloadCloud } from '../models/process-payload-cloud.model';
import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model';
@@ -29,16 +29,11 @@ import { BaseCloudService } from '../../../services/base-cloud.service';
})
export class StartProcessCloudService extends BaseCloudService {
contextRoot: string;
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
constructor(private alfrescoApiService: AlfrescoApiService,
constructor(apiService: AlfrescoApiService,
private logService: LogService,
private appConfigService: AppConfigService) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
appConfigService: AppConfigService) {
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -47,21 +42,13 @@ export class StartProcessCloudService extends BaseCloudService {
* @returns Array of process definitions
*/
getProcessDefinitions(appName: string): Observable<ProcessDefinitionCloud[]> {
if (appName) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/process-definitions`;
const url = `${this.getBasePath(appName)}/rb/v1/process-definitions`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, null,
null, null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
return this.get(url).pipe(
map((res: any) => {
return res.list.entries.map((processDefs) => new ProcessDefinitionCloud(processDefs.entry));
}),
catchError((err) => this.handleProcessError(err))
})
);
} else {
this.logService.error('AppName is mandatory for querying task');
@@ -72,26 +59,14 @@ export class StartProcessCloudService extends BaseCloudService {
/**
* Starts a process based on a process definition, name, form values or variables.
* @param appName name of the Application
* @param requestPayload Details of the process (definition key, name, variables, etc)
* @param payload Details of the process (definition key, name, variables, etc)
* @returns Details of the process instance just started
*/
startProcess(appName: string, requestPayload: ProcessPayloadCloud): Observable<ProcessInstanceCloud> {
startProcess(appName: string, payload: ProcessPayloadCloud): Observable<ProcessInstanceCloud> {
const url = `${this.getBasePath(appName)}/rb/v1/process-instances`;
const queryUrl = `${this.getBasePath(appName)}/rb/v1/process-instances`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
null, null,
null, null, requestPayload,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((processInstance) => new ProcessInstanceCloud(processInstance)),
catchError((err) => this.handleProcessError(err))
return this.post(url, payload).pipe(
map(processInstance => new ProcessInstanceCloud(processInstance))
);
}
private handleProcessError(error: any) {
return throwError(error || 'Server error');
}
}

View File

@@ -15,21 +15,102 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService } from '@alfresco/adf-core';
import { from, Observable } from 'rxjs';
export interface CallApiParams {
path: string;
httpMethod: string;
pathParams?: any;
queryParams?: any;
headerParams?: any;
formParams?: any;
bodyParam?: any;
contentTypes?: string[];
accepts?: string[];
returnType?: any;
contextRoot?: string;
responseType?: string;
}
@Injectable()
export class BaseCloudService {
public contextRoot: string;
protected contextRoot: string;
getBasePath(appName: string) {
if (this.isValidAppName(appName)) {
return `${this.contextRoot}/${appName}`;
}
return this.contextRoot;
protected defaultParams: CallApiParams = {
path: '',
httpMethod: '',
contentTypes: ['application/json'],
accepts: ['application/json'],
returnType: Object
};
constructor(protected apiService: AlfrescoApiService) {}
getBasePath(appName: string): string {
return appName
? `${this.contextRoot}/${appName}`
: this.contextRoot;
}
private isValidAppName(appName: string) {
return appName && appName !== '';
protected post<T, R>(url: string, data?: T): Observable<R> {
return from(
this.callApi<R>({
...this.defaultParams,
path: url,
httpMethod: 'POST',
bodyParam: data
})
);
}
protected put<T, R>(url: string, data?: T): Observable<R> {
return from(
this.callApi<R>({
...this.defaultParams,
path: url,
httpMethod: 'PUT',
bodyParam: data
})
);
}
protected delete(url: string): Observable<void> {
return from(
this.callApi<void>({
...this.defaultParams,
path: url,
httpMethod: 'DELETE'
})
);
}
protected get<T>(url: string, queryParams?: any): Observable<T> {
return from(
this.callApi<T>({
...this.defaultParams,
path: url,
httpMethod: 'GET',
queryParams
})
);
}
protected callApi<T>(params: CallApiParams): Promise<T> {
return this.apiService.getInstance()
.oauth2Auth.callCustomApi(
params.path,
params.httpMethod,
params.pathParams,
params.queryParams,
params.headerParams,
params.formParams,
params.bodyParam,
params.contentTypes,
params.accepts,
params.returnType,
params.contextRoot,
params.responseType
);
}
}

View File

@@ -18,7 +18,7 @@
import { TestBed, async } from '@angular/core/testing';
import { UserPreferenceCloudService } from './user-preference-cloud.service';
import { setupTestBed, CoreModule, AlfrescoApiServiceMock, AppConfigService, LogService, AlfrescoApiService } from '@alfresco/adf-core';
import { setupTestBed, CoreModule, AlfrescoApiServiceMock, AlfrescoApiService } from '@alfresco/adf-core';
import { mockPreferences, getMockPreference, createMockPreference, updateMockPreference } from '../mock/user-preference.mock';
describe('PreferenceService', () => {
@@ -52,7 +52,6 @@ describe('PreferenceService', () => {
CoreModule.forRoot()
],
providers: [
UserPreferenceCloudService, AppConfigService, LogService,
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
]
});
@@ -60,7 +59,6 @@ describe('PreferenceService', () => {
beforeEach(async(() => {
service = TestBed.get(UserPreferenceCloudService);
alfrescoApiMock = TestBed.get(AlfrescoApiService);
service.contextRoot = 'http://{{domain}}.com';
getInstanceSpy = spyOn(alfrescoApiMock, 'getInstance').and.returnValue(apiMock(mockPreferences));
}));

View File

@@ -18,21 +18,18 @@
import { Injectable } from '@angular/core';
import { PreferenceCloudServiceInterface } from './preference-cloud.interface';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { from, throwError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { throwError, Observable } from 'rxjs';
import { BaseCloudService } from './base-cloud.service';
@Injectable()
@Injectable({ providedIn: 'root' })
export class UserPreferenceCloudService extends BaseCloudService implements PreferenceCloudServiceInterface {
contentTypes = ['application/json'];
accepts = ['application/json'];
constructor(
private alfrescoApiService: AlfrescoApiService,
private appConfigService: AppConfigService,
apiService: AlfrescoApiService,
appConfigService: AppConfigService,
private logService: LogService) {
super();
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -42,13 +39,8 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
*/
getPreferences(appName: string): Observable<any> {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName);
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'GET',
null, null, null,
null, null, this.contentTypes,
this.accepts, null, null)
);
const url = `${this.getBasePath(appName)}/preference/v1/preferences`;
return this.get(url);
} else {
this.logService.error('Appname is mandatory for querying preferences');
return throwError('Appname not configured');
@@ -63,14 +55,8 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
*/
getPreferenceByKey(appName: string, key: string): Observable<any> {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
return from(
this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'GET',
null, null, null,
null, null, this.contentTypes,
this.accepts, null, null)
).pipe(catchError((error) => throwError(error)));
const url = `${this.getBasePath(appName)}/preference/v1/preferences/${key}`;
return this.get(url);
} else {
this.logService.error('Appname and key are mandatory for querying preference');
return throwError('Appname not configured');
@@ -86,17 +72,10 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
*/
createPreference(appName: string, key: string, newPreference: any): Observable<any> {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
const requestPayload = JSON.stringify(newPreference);
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'PUT',
null, null,
null, null, requestPayload,
this.contentTypes, this.accepts,
Object, null, null)
).pipe(
catchError((err) => this.handleProcessError(err))
);
const url = `${this.getBasePath(appName)}/preference/v1/preferences/${key}`;
const payload = JSON.stringify(newPreference);
return this.put(url, payload);
} else {
this.logService.error('Appname and key are mandatory for creating preference');
return throwError('Appname not configured');
@@ -122,30 +101,11 @@ export class UserPreferenceCloudService extends BaseCloudService implements Pref
*/
deletePreference(appName: string, key: string): Observable<any> {
if (appName) {
const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`;
return from(this.alfrescoApiService.getInstance()
.oauth2Auth.callCustomApi(uri, 'DELETE',
null, null, null,
null, null, this.contentTypes,
this.accepts, null, null, null)
);
const url = `${this.getBasePath(appName)}/preference/v1/preferences/${key}`;
return this.delete(url);
} else {
this.logService.error('Appname and key are mandatory to delete preference');
return throwError('Appname not configured');
}
}
/**
* Creates preference uri
* @param appName Name of the target app
* @returns String of preference service uri
*/
private buildPreferenceServiceUri(appName: string): string {
this.contextRoot = this.appConfigService.get('bpmHost', '');
return `${this.getBasePath(appName)}/preference/v1/preferences`;
}
private handleProcessError(error: any) {
return throwError(error || 'Server error');
}
}

View File

@@ -16,21 +16,22 @@
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { Observable, from, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { StartTaskCloudRequestModel } from '../start-task/models/start-task-cloud-request.model';
import { TaskDetailsCloudModel, StartTaskCloudResponseModel } from '../start-task/models/task-details-cloud.model';
import { BaseCloudService } from '../../services/base-cloud.service';
@Injectable()
@Injectable({ providedIn: 'root' })
export class StartTaskCloudService extends BaseCloudService {
constructor(
private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
private logService: LogService
) { super(); }
apiService: AlfrescoApiService,
appConfigService: AppConfigService) {
super(apiService);
this.contextRoot = appConfigService.get('bpmHost');
}
/**
* @deprecated in 3.5.0, use TaskCloudService instead.
@@ -39,37 +40,12 @@ export class StartTaskCloudService extends BaseCloudService {
* @returns Details of the newly created task
*/
createNewTask(taskDetails: TaskDetailsCloudModel): Observable<TaskDetailsCloudModel> {
const queryUrl = this.buildCreateTaskUrl(taskDetails.appName);
const bodyParam = JSON.stringify(this.buildRequestBody(taskDetails));
const pathParams = {}, queryParams = {}, headerParams = {},
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
const url = `${this.getBasePath(taskDetails.appName)}/rb/v1/tasks`;
const payload = JSON.stringify(new StartTaskCloudRequestModel(taskDetails));
return from(
this.apiService
.getInstance()
.oauth2Auth.callCustomApi(
queryUrl, 'POST', pathParams, queryParams,
headerParams, formParams, bodyParam,
contentTypes, accepts, null, null)
).pipe(
map((response: StartTaskCloudResponseModel) => {
return new TaskDetailsCloudModel(response.entry);
}),
catchError((err) => this.handleError(err))
return this.post<any, StartTaskCloudResponseModel>(url, payload)
.pipe(
map(response => response.entry)
);
}
private buildCreateTaskUrl(appName: string): any {
this.contextRoot = this.appConfigService.get('bpmHost');
return `${this.getBasePath(appName)}/rb/v1/tasks`;
}
private buildRequestBody(taskDetails: any) {
return new StartTaskCloudRequestModel(taskDetails);
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -17,8 +17,8 @@
import { Injectable } from '@angular/core';
import { AlfrescoApiService, LogService, AppConfigService, IdentityUserService } from '@alfresco/adf-core';
import { from, throwError, Observable, of, Subject } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { throwError, Observable, of, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { TaskDetailsCloudModel, StartTaskCloudResponseModel } from '../start-task/models/task-details-cloud.model';
import { BaseCloudService } from '../../services/base-cloud.service';
import { StartTaskCloudRequestModel } from '../start-task/models/start-task-cloud-request.model';
@@ -28,22 +28,16 @@ import { StartTaskCloudRequestModel } from '../start-task/models/start-task-clou
})
export class TaskCloudService extends BaseCloudService {
contentTypes = ['application/json'];
accepts = ['application/json'];
returnType = Object;
private dataChangesDetected = new Subject();
dataChangesDetected$: Observable<any>;
dataChangesDetected$ = new Subject();
constructor(
private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
apiService: AlfrescoApiService,
appConfigService: AppConfigService,
private logService: LogService,
private identityUserService: IdentityUserService
) {
super();
this.contextRoot = this.appConfigService.get('bpmHost', '');
this.dataChangesDetected$ = this.dataChangesDetected.asObservable();
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
/**
@@ -54,21 +48,10 @@ export class TaskCloudService extends BaseCloudService {
*/
completeTask(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
const queryUrl = this.buildCompleteTaskUrl(appName, taskId);
const bodyParam = { 'payloadType': 'CompleteTaskPayload' };
const pathParams = {}, queryParams = {}, headerParams = {},
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
const url = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/complete`;
const payload = { 'payloadType': 'CompleteTaskPayload' };
return from(
this.apiService
.getInstance()
.oauth2Auth.callCustomApi(
queryUrl, 'POST', pathParams, queryParams,
headerParams, formParams, bodyParam,
contentTypes, accepts, null, null)
).pipe(
catchError((err) => this.handleError(err))
);
return this.post<any, TaskDetailsCloudModel>(url, payload);
} else {
this.logService.error('AppName and TaskId are mandatory for complete a task');
return throwError('AppName/TaskId not configured');
@@ -81,7 +64,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Boolean value if the task can be completed
*/
canCompleteTask(taskDetails: TaskDetailsCloudModel): boolean {
return taskDetails && taskDetails.isAssigned() && this.isAssignedToMe(taskDetails.assignee);
return taskDetails && taskDetails.status === 'ASSIGNED' && this.isAssignedToMe(taskDetails.assignee);
}
/**
@@ -90,7 +73,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Boolean value if the task is editable
*/
isTaskEditable(taskDetails: TaskDetailsCloudModel): boolean {
return taskDetails && taskDetails.isAssigned() && this.isAssignedToMe(taskDetails.assignee);
return taskDetails && taskDetails.status === 'ASSIGNED' && this.isAssignedToMe(taskDetails.assignee);
}
/**
@@ -99,7 +82,7 @@ export class TaskCloudService extends BaseCloudService {
* @returns Boolean value if the task can be completed
*/
canClaimTask(taskDetails: TaskDetailsCloudModel): boolean {
return taskDetails && taskDetails.canClaimTask();
return taskDetails && taskDetails.status === 'CREATED';
}
/**
@@ -109,7 +92,7 @@ export class TaskCloudService extends BaseCloudService {
*/
canUnclaimTask(taskDetails: TaskDetailsCloudModel): boolean {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return taskDetails && taskDetails.canUnclaimTask(currentUser);
return taskDetails && taskDetails.status === 'ASSIGNED' && taskDetails.assignee === currentUser;
}
/**
@@ -122,18 +105,12 @@ export class TaskCloudService extends BaseCloudService {
claimTask(appName: string, taskId: string, assignee: string): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/claim?assignee=${assignee}`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
return this.post(queryUrl).pipe(
map((res: any) => {
this.dataChangesDetected.next();
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
this.dataChangesDetected$.next();
return res.entry;
})
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
@@ -150,18 +127,12 @@ export class TaskCloudService extends BaseCloudService {
unclaimTask(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/release`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'POST',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
return this.post(queryUrl).pipe(
map((res: any) => {
this.dataChangesDetected.next();
return new TaskDetailsCloudModel(res.entry);
}),
catchError((err) => this.handleError(err))
this.dataChangesDetected$.next();
return res.entry;
})
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
@@ -178,17 +149,9 @@ export class TaskCloudService extends BaseCloudService {
getTaskById(appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}`;
return from(this.apiService.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))
return this.get(queryUrl).pipe(
map((res: any) => res.entry)
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
@@ -202,23 +165,12 @@ export class TaskCloudService extends BaseCloudService {
* @returns Details of the newly created task
*/
createNewTask(startTaskRequest: StartTaskCloudRequestModel, appName: string): Observable<TaskDetailsCloudModel> {
const queryUrl = this.buildCreateTaskUrl(appName);
const bodyParam = JSON.stringify(this.buildRequestBody(startTaskRequest));
const pathParams = {}, queryParams = {}, headerParams = {},
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks`;
const payload = JSON.stringify(new StartTaskCloudRequestModel(startTaskRequest));
return from(
this.apiService
.getInstance()
.oauth2Auth.callCustomApi(
queryUrl, 'POST', pathParams, queryParams,
headerParams, formParams, bodyParam,
contentTypes, accepts, null, null)
).pipe(
map((response: StartTaskCloudResponseModel) => {
return new TaskDetailsCloudModel(response.entry);
}),
catchError((err) => this.handleError(err))
return this.post<any, StartTaskCloudResponseModel>(queryUrl, payload)
.pipe(
map(response => response.entry)
);
}
@@ -226,25 +178,16 @@ export class TaskCloudService extends BaseCloudService {
* Updates the details (name, description, due date) for a task.
* @param appName Name of the app
* @param taskId ID of the task to update
* @param updatePayload Data to update the task
* @param payload Data to update the task
* @returns Updated task details
*/
updateTask(appName: string, taskId: string, updatePayload: any): Observable<TaskDetailsCloudModel> {
updateTask(appName: string, taskId: string, payload: any): Observable<TaskDetailsCloudModel> {
if (appName && taskId) {
updatePayload.payloadType = 'UpdateTaskPayload';
payload.payloadType = 'UpdateTaskPayload';
const queryUrl = `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}`;
return from(this.apiService.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))
return this.put(queryUrl, payload).pipe(
map((res: any) => res.entry)
);
} else {
this.logService.error('AppName and TaskId are mandatory for querying a task');
@@ -261,18 +204,7 @@ export class TaskCloudService extends BaseCloudService {
getCandidateUsers(appName: string, taskId: string): Observable<string[]> {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-users`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((response: string[]) => {
return response;
}),
catchError((err) => this.handleError(err))
);
return this.get<string[]>(queryUrl);
} else {
this.logService.error('AppName and TaskId are mandatory to get candidate user');
return of([]);
@@ -288,18 +220,7 @@ export class TaskCloudService extends BaseCloudService {
getCandidateGroups(appName: string, taskId: string): Observable<string[]> {
if (appName && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-groups`;
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, null, null,
null, null,
this.contentTypes, this.accepts,
this.returnType, null, null)
).pipe(
map((response: string[]) => {
return response;
}),
catchError((err) => this.handleError(err))
);
return this.get<string[]>(queryUrl);
} else {
this.logService.error('AppName and TaskId are mandatory to get candidate groups');
return of([]);
@@ -310,23 +231,4 @@ export class TaskCloudService extends BaseCloudService {
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return assignee === currentUser;
}
private buildCompleteTaskUrl(appName: string, taskId: string): string {
return `${this.getBasePath(appName)}/rb/v1/tasks/${taskId}/complete`;
}
private buildCreateTaskUrl(appName: string): string {
this.contextRoot = this.appConfigService.get('bpmHost');
return `${this.getBasePath(appName)}/rb/v1/tasks`;
}
private buildRequestBody(startTaskRequest: StartTaskCloudRequestModel) {
return new StartTaskCloudRequestModel(startTaskRequest);
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -15,98 +15,45 @@
* limitations under the License.
*/
export class TaskDetailsCloudModel {
id: string;
name: string;
appName: string;
assignee: string;
appVersion: string;
createdDate: Date;
claimedDate: Date;
completedDate: Date;
formKey: any;
category: any;
description: string;
dueDate: Date;
lastModified: Date;
lastModifiedTo: Date;
lastModifiedFrom: Date;
owner: any;
parentTaskId: number;
priority: number;
processDefinitionId: string;
processInstanceId: string;
status: TaskStatusEnum;
standalone: boolean;
candidateUsers: string[];
candidateGroups: string[];
managerOfCandidateGroup: boolean;
memberOfCandidateGroup: boolean;
memberOfCandidateUsers: boolean;
export interface TaskDetailsCloudModel {
id?: string;
name?: string;
appName?: string;
assignee?: string;
appVersion?: number;
createdDate?: Date;
claimedDate?: Date;
completedDate?: Date;
formKey?: any;
category?: any;
description?: string;
dueDate?: Date;
lastModified?: Date;
lastModifiedTo?: Date;
lastModifiedFrom?: Date;
owner?: any;
parentTaskId?: number;
priority?: number;
processDefinitionId?: string;
processInstanceId?: string;
status?: TaskStatus;
standalone?: boolean;
candidateUsers?: string[];
candidateGroups?: string[];
managerOfCandidateGroup?: boolean;
memberOfCandidateGroup?: boolean;
memberOfCandidateUsers?: boolean;
processDefinitionDeploymentId?: string;
constructor(obj?: any) {
if (obj) {
this.id = obj.id || null;
this.name = obj.name || null;
this.appName = obj.appName || null;
this.assignee = obj.assignee || null;
this.appVersion = obj.appVersion || null;
this.createdDate = obj.createdDate || null;
this.claimedDate = obj.claimedDate || null;
this.completedDate = obj.completedDate || null;
this.formKey = obj.formKey || null;
this.description = obj.description || null;
this.dueDate = obj.dueDate || null;
this.lastModified = obj.lastModified || null;
this.lastModifiedTo = obj.lastModifiedTo || null;
this.lastModifiedFrom = obj.lastModifiedFrom || null;
this.owner = obj.owner || null;
this.parentTaskId = obj.parentTaskId || null;
this.priority = obj.priority || null;
this.processDefinitionId = obj.processDefinitionId || null;
this.processInstanceId = obj.processInstanceId || null;
this.status = obj.status || null;
this.standalone = obj.standalone || null;
this.candidateUsers = obj.candidateUsers || null;
this.candidateGroups = obj.candidateGroups || null;
this.managerOfCandidateGroup = obj.managerOfCandidateGroup || null;
this.memberOfCandidateGroup = obj.memberOfCandidateGroup || null;
this.memberOfCandidateUsers = obj.memberOfCandidateUsers || null;
}
}
isCompleted(): boolean {
return this.status === TaskStatusEnum.COMPLETED;
}
isCancelled(): boolean {
return this.status === TaskStatusEnum.CANCELLED;
}
isAssigned(): boolean {
return this.status === TaskStatusEnum.ASSIGNED;
}
canClaimTask(): boolean {
return this.status === TaskStatusEnum.CREATED;
}
canUnclaimTask(user: string): boolean {
return this.isAssigned() && this.assignee === user;
}
}
export interface StartTaskCloudResponseModel {
entry: TaskDetailsCloudModel;
}
export enum TaskStatusEnum {
COMPLETED= 'COMPLETED',
DELETED = 'DELETED',
CREATED = 'CREATED',
ASSIGNED = 'ASSIGNED',
SUSPENDED = 'SUSPENDED',
CANCELLED = 'CANCELLED'
}
export type TaskStatus = |
'COMPLETED' |
'DELETED' |
'CREATED' |
'ASSIGNED' |
'SUSPENDED' |
'CANCELLED';

View File

@@ -1,6 +1,7 @@
<div *ngIf="!loading; else loadingTemplate">
<adf-cloud-form *ngIf="hasForm(); else withoutForm"
[appName]="appName"
[appVersion]="taskDetails.appVersion"
[taskId]="taskId"
[processInstanceId]="taskDetails.processInstanceId"
[readOnly]="isReadOnly()"

View File

@@ -27,11 +27,11 @@ import { TaskFormCloudComponent } from './task-form-cloud.component';
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
import { TaskCloudService } from '../../services/task-cloud.service';
const taskDetails = {
const taskDetails: TaskDetailsCloudModel = {
appName: 'simple-app',
assignee: 'admin.adf',
completedDate: null,
createdDate: 1555419255340,
createdDate: new Date(1555419255340),
description: null,
formKey: null,
id: 'bd6b1741-6046-11e9-80f0-0a586460040d',
@@ -63,7 +63,7 @@ describe('TaskFormCloudComponent', () => {
identityUserService = TestBed.get(IdentityUserService);
getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({ username: 'admin.adf' });
taskCloudService = TestBed.get(TaskCloudService);
getTaskSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
getTaskSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(taskDetails));
spyOn(taskCloudService, 'getCandidateGroups').and.returnValue(of([]));
spyOn(taskCloudService, 'getCandidateUsers').and.returnValue(of([]));
@@ -153,8 +153,8 @@ describe('TaskFormCloudComponent', () => {
it('should not show unclaim button when status is not ASSIGNED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = '';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
taskDetails.status = undefined;
getTaskSpy.and.returnValue(of(taskDetails));
component.loadTask();
fixture.detectChanges();
@@ -168,7 +168,7 @@ describe('TaskFormCloudComponent', () => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = 'CREATED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
getTaskSpy.and.returnValue(of(taskDetails));
component.loadTask();
fixture.detectChanges();
@@ -181,8 +181,8 @@ describe('TaskFormCloudComponent', () => {
it('should not show claim button when status is not CREATED', async(() => {
component.appName = 'app1';
component.taskId = 'task1';
taskDetails.status = '';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
taskDetails.status = undefined;
getTaskSpy.and.returnValue(of(taskDetails));
component.loadTask();
fixture.detectChanges();
@@ -306,7 +306,7 @@ describe('TaskFormCloudComponent', () => {
it('should emit taskClaimed when task is claimed', (done) => {
spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
taskDetails.status = 'CREATED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
getTaskSpy.and.returnValue(of(taskDetails));
component.appName = 'app1';
component.taskId = 'task1';
@@ -354,7 +354,7 @@ describe('TaskFormCloudComponent', () => {
const reloadSpy = spyOn(component, 'loadTask').and.callThrough();
taskDetails.status = 'CREATED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
getTaskSpy.and.returnValue(of(taskDetails));
component.appName = 'app1';
component.taskId = 'task1';
@@ -373,7 +373,7 @@ describe('TaskFormCloudComponent', () => {
spyOn(component, 'hasCandidateUsers').and.returnValue(true);
taskDetails.status = 'ASSIGNED';
getTaskSpy.and.returnValue(of(new TaskDetailsCloudModel(taskDetails)));
getTaskSpy.and.returnValue(of(taskDetails));
component.appName = 'app1';
component.taskId = 'task1';

View File

@@ -120,32 +120,25 @@ export class TaskFormCloudComponent implements OnChanges {
this.loadTask();
return;
}
}
loadTask() {
this.loading = true;
this.taskCloudService.getTaskById(this.appName, this.taskId).subscribe((details: TaskDetailsCloudModel) => {
this.taskDetails = details;
this.loading = false;
});
this.taskCloudService.getCandidateUsers(this.appName, this.taskId).subscribe((users: string[]) => {
if (users) {
this.candidateUsers = users;
}
});
this.taskCloudService
.getTaskById(this.appName, this.taskId)
.subscribe(details => {
this.taskDetails = details;
this.loading = false;
});
this.taskCloudService.getCandidateGroups(this.appName, this.taskId).subscribe((groups: string[]) => {
if (groups) {
this.candidateGroups = groups;
}
});
this.taskCloudService
.getCandidateUsers(this.appName, this.taskId)
.subscribe(users => this.candidateUsers = users || []);
}
private reloadTask() {
this.loadTask();
this.taskCloudService
.getCandidateGroups(this.appName, this.taskId)
.subscribe(groups => this.candidateGroups = groups || []);
}
hasForm(): boolean {
@@ -186,17 +179,17 @@ export class TaskFormCloudComponent implements OnChanges {
}
onCompleteTask() {
this.reloadTask();
this.loadTask();
this.taskCompleted.emit(this.taskId);
}
onClaimTask() {
this.reloadTask();
this.loadTask();
this.taskClaimed.emit(this.taskId);
}
onUnclaimTask() {
this.reloadTask();
this.loadTask();
this.taskUnclaimed.emit(this.taskId);
}

View File

@@ -28,7 +28,7 @@ import {
CardViewUpdateService,
CardViewDatetimeItemModel
} from '@alfresco/adf-core';
import { TaskDetailsCloudModel, TaskStatusEnum } from '../../start-task/models/task-details-cloud.model';
import { TaskDetailsCloudModel, TaskStatus } from '../../start-task/models/task-details-cloud.model';
import { Router } from '@angular/router';
import { TaskCloudService } from '../../services/task-cloud.service';
import { Subject } from 'rxjs';
@@ -62,7 +62,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges {
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
taskDetails: TaskDetailsCloudModel = new TaskDetailsCloudModel();
taskDetails: TaskDetailsCloudModel = {};
properties: CardViewItem[];
inEdit: boolean = false;
parentTaskName: string;
@@ -99,7 +99,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges {
}
ngOnChanges() {
this.taskDetails = new TaskDetailsCloudModel();
this.taskDetails = {};
if (this.appName && this.taskId) {
this.loadTaskDetailsById(this.appName, this.taskId);
} else {
@@ -278,8 +278,8 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges {
);
}
isCompleted() {
return this.taskDetails && this.taskDetails.status && this.taskDetails.status.toUpperCase() === TaskStatusEnum.COMPLETED;
isCompleted(): boolean {
return this.taskDetails && this.taskDetails.status === 'COMPLETED';
}
hasAssignee(): boolean {
@@ -299,7 +299,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges {
}
isClickable(): boolean {
const states = [TaskStatusEnum.ASSIGNED, TaskStatusEnum.CREATED, TaskStatusEnum.SUSPENDED];
const states: TaskStatus[] = ['ASSIGNED', 'CREATED', 'SUSPENDED'];
return states.includes(this.taskDetails.status);
}

View File

@@ -17,80 +17,74 @@
import { TaskDetailsCloudModel } from '../../start-task/models/task-details-cloud.model';
export const assignedTaskDetailsCloudMock = new TaskDetailsCloudModel(
{
'appName': 'task-app',
'appVersion': '',
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'AssignedTaskUser',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': 1545048055900,
'dueDate': Date.now(),
'claimedDate': null,
'priority': 5,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'ASSIGNED',
'owner': 'ownerUser',
'parentTaskId': null,
'formKey': null,
'lastModified': 1545048055900,
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
}
);
export const assignedTaskDetailsCloudMock: TaskDetailsCloudModel = {
'appName': 'task-app',
'appVersion': 1,
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'AssignedTaskUser',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': new Date(1545048055900),
'dueDate': new Date(),
'claimedDate': null,
'priority': 5,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'ASSIGNED',
'owner': 'ownerUser',
'parentTaskId': null,
'formKey': null,
'lastModified': new Date(1545048055900),
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
};
export const createdTaskDetailsCloudMock = new TaskDetailsCloudModel(
{
'appName': 'task-app',
'appVersion': '',
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'CreatedTaskUser',
'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': 'CREATED',
'owner': 'ownerUser',
'parentTaskId': null,
'formKey': null,
'lastModified': 1545048055900,
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
}
);
export const createdTaskDetailsCloudMock: TaskDetailsCloudModel = {
'appName': 'task-app',
'appVersion': 1,
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'CreatedTaskUser',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': new Date(1545048055900),
'dueDate': new Date(1545091200000),
'claimedDate': null,
'priority': 5,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'CREATED',
'owner': 'ownerUser',
'parentTaskId': null,
'formKey': null,
'lastModified': new Date(1545048055900),
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
};
export const emptyOwnerTaskDetailsCloudMock = new TaskDetailsCloudModel(
{
'appName': 'task-app',
'appVersion': '',
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'AssignedTaskUser',
'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': null,
'parentTaskId': null,
'formKey': null,
'lastModified': 1545048055900,
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
}
);
export const emptyOwnerTaskDetailsCloudMock: TaskDetailsCloudModel = {
'appName': 'task-app',
'appVersion': 1,
'id': '68d54a8f-01f3-11e9-8e36-0a58646002ad',
'assignee': 'AssignedTaskUser',
'name': 'This is a new task ',
'description': 'This is the description ',
'createdDate': new Date(1545048055900),
'dueDate': new Date(1545091200000),
'claimedDate': null,
'priority': 5,
'category': null,
'processDefinitionId': null,
'processInstanceId': null,
'status': 'ASSIGNED',
'owner': null,
'parentTaskId': null,
'formKey': null,
'lastModified': new Date(1545048055900),
'lastModifiedTo': null,
'lastModifiedFrom': null,
'standalone': true
};

View File

@@ -18,53 +18,40 @@
import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model';
import { Observable, from, throwError } from 'rxjs';
import { Observable, throwError } from 'rxjs';
import { TaskListCloudSortingModel } from '../models/task-list-sorting.model';
import { BaseCloudService } from '../../../services/base-cloud.service';
@Injectable()
@Injectable({ providedIn: 'root' })
export class TaskListCloudService extends BaseCloudService {
constructor(private apiService: AlfrescoApiService,
private appConfigService: AppConfigService,
constructor(apiService: AlfrescoApiService,
appConfigService: AppConfigService,
private logService: LogService) {
super();
super(apiService);
this.contextRoot = appConfigService.get('bpmHost', '');
}
contentTypes = ['application/json'];
accepts = ['application/json'];
/**
* Finds a task using an object with optional query properties.
* @param requestNode Query object
* @returns Task information
*/
getTaskByRequest(requestNode: TaskQueryCloudRequestModel): Observable<any> {
if (requestNode.appName) {
const queryUrl = this.buildQueryUrl(requestNode);
const queryUrl = `${this.getBasePath(requestNode.appName)}/query/v1/tasks`;
const queryParams = this.buildQueryParams(requestNode);
const sortingParams = this.buildSortingParam(requestNode.sorting);
if (sortingParams) {
queryParams['sort'] = sortingParams;
}
return from(this.apiService.getInstance()
.oauth2Auth.callCustomApi(queryUrl, 'GET',
null, queryParams, null,
null, null, ['application/json'],
['application/json'], null, null)
);
return this.get(queryUrl, queryParams);
} else {
this.logService.error('Appname is mandatory for querying task');
return throwError('Appname not configured');
}
}
private buildQueryUrl(requestNode: TaskQueryCloudRequestModel) {
this.contextRoot = this.appConfigService.get('bpmHost', '');
return `${this.getBasePath(requestNode.appName)}/query/v1/tasks`;
}
private buildQueryParams(requestNode: TaskQueryCloudRequestModel): Object {
const queryParam: Object = {};
for (const property in requestNode) {