[ACS-7427] Process Services improvements and cleanup (#9664)

This commit is contained in:
Denys Vuika
2024-05-20 16:08:47 -04:00
committed by GitHub
parent 96e607b4de
commit e71e2a749a
174 changed files with 1736 additions and 3933 deletions

View File

@@ -23,10 +23,10 @@ import { of, throwError } from 'rxjs';
import { defaultApp, deployedApps, nonDeployedApps } from '../mock/apps-list.mock';
import { AppsListComponent, APP_LIST_LAYOUT_GRID, APP_LIST_LAYOUT_LIST } from './apps-list.component';
import { ProcessTestingModule } from '../testing/process.testing.module';
import { AppDefinitionRepresentationModel } from '../task-list';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing';
import { AppDefinitionRepresentation } from '@alfresco/js-api';
describe('AppsListComponent', () => {
let loader: HarnessLoader;
@@ -61,6 +61,10 @@ describe('AppsListComponent', () => {
loader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});
afterEach(() => {
fixture.destroy();
});
it('should define layoutType with the default value', () => {
component.layoutType = '';
fixture.detectChanges();
@@ -88,16 +92,27 @@ describe('AppsListComponent', () => {
it('should show the apps filtered by defaultAppId', () => {
component.filtersAppId = [{ defaultAppId: 'fake-app-1' }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
expect(component.appList.length).toEqual(1);
});
it('should show the apps filtered by deploymentId', () => {
it('should filter apps by defaultAppId', async () => {
const filtered = component.filterApps(deployedApps, [{ defaultAppId: 'fake-app-1' }]);
expect(filtered.length).toEqual(1);
expect(filtered[0].defaultAppId).toEqual('fake-app-1');
});
it('should filter apps by deploymentId', async () => {
const filtered = component.filterApps(deployedApps, [{ deploymentId: '4' }]);
expect(filtered.length).toEqual(1);
expect(filtered[0].deploymentId).toEqual('4');
});
it('should show the apps filtered by deploymentId', async () => {
component.filtersAppId = [{ deploymentId: '4' }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
await fixture.whenStable();
expect(component.appList.length).toEqual(1);
expect(component.appList[0].deploymentId).toEqual('4');
});
@@ -105,8 +120,6 @@ describe('AppsListComponent', () => {
it('should show the apps filtered by name', () => {
component.filtersAppId = [{ name: 'App5' }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
expect(component.appList.length).toEqual(1);
expect(component.appList[0].name).toEqual('App5');
});
@@ -114,8 +127,6 @@ describe('AppsListComponent', () => {
it('should show the apps filtered by id', () => {
component.filtersAppId = [{ id: 6 }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
expect(component.appList.length).toEqual(1);
expect(component.appList[0].id).toEqual(6);
});
@@ -123,8 +134,6 @@ describe('AppsListComponent', () => {
it('should show the apps filtered by modelId', () => {
component.filtersAppId = [{ modelId: 66 }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
expect(component.appList.length).toEqual(2);
expect(component.appList[0].modelId).toEqual(66);
});
@@ -132,8 +141,6 @@ describe('AppsListComponent', () => {
it('should show the apps filtered by tenantId', () => {
component.filtersAppId = [{ tenantId: 9 }];
fixture.detectChanges();
expect(component.isEmpty()).toBe(false);
expect(component.appList).toBeDefined();
expect(component.appList.length).toEqual(2);
expect(component.appList[0].tenantId).toEqual(9);
});
@@ -147,19 +154,19 @@ describe('AppsListComponent', () => {
describe('internationalization', () => {
it('should provide a translation for the default application name, when app name is not provided', () => {
const appDataMock = {
const appDataMock: AppDefinitionRepresentation = {
defaultAppId: 'tasks',
name: null
} as AppDefinitionRepresentationModel;
};
expect(component.getAppName(appDataMock)).toBe('ADF_TASK_LIST.APPS.TASK_APP_NAME');
});
it('should provide the application name, when it exists', () => {
const appDataMock = {
const appDataMock: AppDefinitionRepresentation = {
defaultAppId: 'uiu',
name: 'the-name'
} as AppDefinitionRepresentationModel;
};
expect(component.getAppName(appDataMock)).toBe(appDataMock.name);
});

View File

@@ -18,10 +18,9 @@
import { CustomEmptyContentTemplateDirective, EmptyContentComponent } from '@alfresco/adf-core';
import { AppsProcessService } from './services/apps-process.service';
import { AfterContentInit, Component, EventEmitter, Input, OnInit, Output, ContentChild, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Observable, Observer, Subject } from 'rxjs';
import { AppDefinitionRepresentationModel } from '../task-list';
import { Subject } from 'rxjs';
import { IconModel } from './icon.model';
import { share, takeUntil, finalize } from 'rxjs/operators';
import { finalize, map } from 'rxjs/operators';
import { AppDefinitionRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { MatListModule } from '@angular/material/list';
@@ -30,7 +29,6 @@ import { MatCardModule } from '@angular/material/card';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core';
const DEFAULT_TASKS_APP: string = 'tasks';
const DEFAULT_TASKS_APP_NAME: string = 'ADF_TASK_LIST.APPS.TASK_APP_NAME';
const DEFAULT_TASKS_APP_THEME: string = 'theme-2';
const DEFAULT_TASKS_APP_ICON: string = 'glyphicon-asterisk';
@@ -58,39 +56,37 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
@Input()
layoutType: string = APP_LIST_LAYOUT_GRID;
/** The default app to show when the component is loaded. */
@Input()
defaultAppId = 'tasks';
/** Provides a way to filter the apps to show. */
@Input()
filtersAppId: any[];
filtersAppId: AppDefinitionRepresentation[];
/** Emitted when an app entry is clicked. */
@Output()
appClick = new EventEmitter<AppDefinitionRepresentationModel>();
appClick = new EventEmitter<AppDefinitionRepresentation>();
/** Emitted when an error occurs. */
@Output()
error = new EventEmitter<any>();
apps$: Observable<AppDefinitionRepresentationModel>;
currentApp: AppDefinitionRepresentationModel;
appList: AppDefinitionRepresentationModel[] = [];
currentApp: AppDefinitionRepresentation;
appList: AppDefinitionRepresentation[] = [];
loading: boolean = false;
hasEmptyCustomContentTemplate: boolean = false;
private appsObserver: Observer<AppDefinitionRepresentation>;
private iconsMDL: IconModel;
private onDestroy$ = new Subject<boolean>();
constructor(private appsProcessService: AppsProcessService) {
this.apps$ = new Observable<AppDefinitionRepresentationModel>((observer) => (this.appsObserver = observer)).pipe(share());
}
constructor(private appsProcessService: AppsProcessService) {}
ngOnInit() {
if (!this.isValidType()) {
this.setDefaultLayoutType();
}
this.apps$.pipe(takeUntil(this.onDestroy$)).subscribe((app) => this.appList.push(app));
this.iconsMDL = new IconModel();
this.load();
}
@@ -106,11 +102,11 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
}
}
isDefaultApp(app: AppDefinitionRepresentation): boolean {
return app.defaultAppId === DEFAULT_TASKS_APP;
private isDefaultApp(app: AppDefinitionRepresentation): boolean {
return app.defaultAppId === this.defaultAppId;
}
getAppName(app: AppDefinitionRepresentationModel): string {
getAppName(app: AppDefinitionRepresentation): string {
return this.isDefaultApp(app) ? DEFAULT_TASKS_APP_NAME : app.name;
}
@@ -119,7 +115,7 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
*
* @param app application model
*/
selectApp(app: AppDefinitionRepresentationModel) {
selectApp(app: AppDefinitionRepresentation) {
this.currentApp = app;
this.appClick.emit(app);
}
@@ -176,11 +172,11 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
return this.loading;
}
getTheme(app: AppDefinitionRepresentationModel): string {
getTheme(app: AppDefinitionRepresentation): string {
return app.theme ? app.theme : '';
}
getBackgroundIcon(app: AppDefinitionRepresentationModel): string {
getBackgroundIcon(app: AppDefinitionRepresentation): string {
return this.iconsMDL.mapGlyphiconToMaterialDesignIcons(app.icon);
}
@@ -188,18 +184,22 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
this.loading = true;
this.appsProcessService
.getDeployedApplications()
.pipe(finalize(() => (this.loading = false)))
.pipe(
map((apps) => apps.filter((app) => app.deploymentId !== undefined || app.defaultAppId === this.defaultAppId)),
finalize(() => (this.loading = false))
)
.subscribe(
(res) => {
this.filterApps(res).forEach((app) => {
const apps = this.filterApps(res, this.filtersAppId).map((app) => {
if (this.isDefaultApp(app)) {
app.theme = DEFAULT_TASKS_APP_THEME;
app.icon = DEFAULT_TASKS_APP_ICON;
this.appsObserver.next(app);
} else if (app.deploymentId) {
this.appsObserver.next(app);
}
return app;
});
this.appList = [...apps];
},
(err) => {
this.error.emit(err);
@@ -207,28 +207,19 @@ export class AppsListComponent implements OnInit, AfterContentInit, OnDestroy {
);
}
private filterApps(apps: AppDefinitionRepresentation[]): AppDefinitionRepresentation[] {
if (this.filtersAppId) {
const filteredApps: AppDefinitionRepresentation[] = [];
apps.forEach((app) => {
this.filtersAppId.forEach((filter) => {
if (
app.defaultAppId === filter.defaultAppId ||
app.deploymentId === filter.deploymentId ||
app.name === filter.name ||
app.id === filter.id ||
app.modelId === filter.modelId ||
app.tenantId === filter.tenantId
) {
filteredApps.push(app);
}
});
});
return filteredApps;
}
return apps;
filterApps(apps: AppDefinitionRepresentation[], filter: Partial<AppDefinitionRepresentation>[]): AppDefinitionRepresentation[] {
return filter && filter.length > 0
? apps.filter((app) =>
filter.some(
(f) =>
(f.defaultAppId && app.defaultAppId === f.defaultAppId) ||
(f.deploymentId && app.deploymentId === f.deploymentId) ||
(f.name && app.name === f.name) ||
(f.id !== undefined && app.id === f.id) ||
(f.modelId !== undefined && app.modelId === f.modelId) ||
(f.tenantId !== undefined && app.tenantId === f.tenantId)
)
)
: apps;
}
}

View File

@@ -39,7 +39,7 @@ export class AppsProcessService {
* @returns The list of deployed apps
*/
getDeployedApplications(): Observable<AppDefinitionRepresentation[]> {
return from(this.appsApi.getAppDefinitions()).pipe(map((response) => response.data));
return from(this.appsApi.getAppDefinitions()).pipe(map((response) => response.data || []));
}
/**
@@ -49,16 +49,6 @@ export class AppsProcessService {
* @returns The list of deployed apps
*/
getDeployedApplicationsByName(name: string): Observable<AppDefinitionRepresentation> {
return from(this.appsApi.getAppDefinitions()).pipe(map((response) => response.data.find((app) => app.name === name)));
}
/**
* Gets the details for a specific app ID number.
*
* @param appId ID of the target app
* @returns Details of the app
*/
getApplicationDetailsById(appId: number): Observable<AppDefinitionRepresentation> {
return from(this.appsApi.getAppDefinitions()).pipe(map((response) => response.data.find((app) => app.id === appId)));
return this.getDeployedApplications().pipe(map((response) => response.find((app) => app.name === name)));
}
}

View File

@@ -34,7 +34,7 @@ export class CreateProcessAttachmentComponent implements OnChanges {
* from the user within the component.
*/
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
error = new EventEmitter<any>();
/**
* Emitted when an attachment is successfully created or uploaded
@@ -43,7 +43,7 @@ export class CreateProcessAttachmentComponent implements OnChanges {
@Output()
success = new EventEmitter<RelatedContentRepresentation>();
constructor(private activitiContentService: ProcessContentService) {}
constructor(private processContentService: ProcessContentService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes['processInstanceId']?.currentValue) {
@@ -59,7 +59,7 @@ export class CreateProcessAttachmentComponent implements OnChanges {
const opts = {
isRelatedContent: true
};
this.activitiContentService.createProcessRelatedContent(this.processInstanceId, file, opts).subscribe(
this.processContentService.createProcessRelatedContent(this.processInstanceId, file, opts).subscribe(
(res) => {
this.success.emit(res);
},

View File

@@ -33,7 +33,7 @@ export class AttachmentComponent implements OnChanges {
* attachment from the user within the component.
*/
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
error = new EventEmitter<any>();
/**
* Emitted when an attachment is created or uploaded successfully
@@ -42,7 +42,7 @@ export class AttachmentComponent implements OnChanges {
@Output()
success = new EventEmitter<any>();
constructor(private activitiContentService: ProcessContentService) {}
constructor(private processContentService: ProcessContentService) {}
ngOnChanges(changes: SimpleChanges) {
if (changes['taskId']?.currentValue) {
@@ -58,7 +58,7 @@ export class AttachmentComponent implements OnChanges {
const opts = {
isRelatedContent: true
};
this.activitiContentService.createTaskRelatedContent(this.taskId, file, opts).subscribe(
this.processContentService.createTaskRelatedContent(this.taskId, file, opts).subscribe(
(res) => {
this.success.emit(res);
},

View File

@@ -7,8 +7,8 @@
<adf-no-content-template>
<ng-template>
<ng-content *ngIf="hasCustomTemplate; else defaulEmptyList" class="adf-custom-empty-template"></ng-content>
<ng-template #defaulEmptyList>
<ng-content *ngIf="hasCustomTemplate; else defaultEmptyList" class="adf-custom-empty-template"></ng-content>
<ng-template #defaultEmptyList>
<adf-empty-list>
<div adf-empty-list-header class="adf-empty-list-header">
{{'ADF_PROCESS_LIST.PROCESS-ATTACHMENT.EMPTY.HEADER' | translate}}
@@ -19,7 +19,7 @@
</adf-no-content-template>
<data-columns>
<data-column key="icon" type="image" srTitle="ADF_PROCESS_LIST.PROPERTIES.THUMBNAIL" [sortable]="false"></data-column>
<data-column key="icon" type="image" [sr-title]="'ADF_PROCESS_LIST.PROPERTIES.THUMBNAIL'" [sortable]="false"></data-column>
<data-column key="name" type="text" title="{{'ADF_PROCESS_LIST.PROPERTIES.NAME' | translate}}" class="adf-full-width adf-ellipsis-cell" [sortable]="true"></data-column>
<data-column key="created" type="date" format="shortDate" title="{{'ADF_PROCESS_LIST.PROPERTIES.CREATED' | translate}}"></data-column>
</data-columns>

View File

@@ -77,7 +77,7 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
isLoading: boolean = false;
constructor(
private activitiContentService: ProcessContentService,
private processContentService: ProcessContentService,
private downloadService: DownloadService,
private thumbnailService: ThumbnailService,
private ngZone: NgZone
@@ -105,10 +105,6 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
});
}
hasCustomEmptyTemplate(): boolean {
return !!this.emptyTemplate;
}
add(content: any): void {
this.ngZone.run(() => {
this.attachments.push({
@@ -121,10 +117,6 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
});
}
isEmpty(): boolean {
return this.attachments?.length === 0;
}
onShowRowActionsMenu(event: any) {
const viewAction = {
title: 'ADF_PROCESS_LIST.MENU_ACTIONS.VIEW_CONTENT',
@@ -166,7 +158,7 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
}
emitDocumentContent(content: any) {
this.activitiContentService.getContentPreview(content.id).subscribe(
this.processContentService.getContentPreview(content.id).subscribe(
(blob: Blob) => {
content.contentBlob = blob;
this.attachmentClick.emit(content);
@@ -178,7 +170,7 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
}
downloadContent(content: any): void {
this.activitiContentService.getFileRawContent(content.id).subscribe(
this.processContentService.getFileRawContent(content.id).subscribe(
(blob: Blob) => this.downloadService.downloadBlob(blob, content.name),
(err) => {
this.error.emit(err);
@@ -186,17 +178,13 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
);
}
isDisabled(): boolean {
return this.disabled;
}
private loadAttachmentsByProcessInstanceId(processInstanceId: string) {
if (processInstanceId) {
this.reset();
this.isLoading = true;
const isRelatedContent = 'true';
this.activitiContentService.getProcessRelatedContent(processInstanceId, { isRelatedContent }).subscribe(
(res: any) => {
this.processContentService.getProcessRelatedContent(processInstanceId, { isRelatedContent }).subscribe(
(res) => {
res.data.forEach((content) => {
this.attachments.push({
id: content.id,
@@ -219,7 +207,7 @@ export class ProcessAttachmentListComponent implements OnChanges, AfterContentIn
private deleteAttachmentById(contentId: number) {
if (contentId) {
this.activitiContentService.deleteRelatedContent(contentId).subscribe(
this.processContentService.deleteRelatedContent(contentId).subscribe(
() => {
this.attachments = this.attachments.filter((content) => content.id !== contentId);
},

View File

@@ -6,8 +6,8 @@
(executeRowAction)="onExecuteRowAction($event)">
<adf-no-content-template>
<ng-template>
<ng-content *ngIf="hasCustomTemplate; else defaulEmptyList" class="adf-custom-empty-template"></ng-content>
<ng-template #defaulEmptyList>
<ng-content *ngIf="hasCustomTemplate; else defaultEmptyList" class="adf-custom-empty-template"></ng-content>
<ng-template #defaultEmptyList>
<adf-empty-list>
<div adf-empty-list-header class="adf-empty-list-header">
{{'ADF_TASK_LIST.ATTACHMENT.EMPTY.HEADER' | translate}}
@@ -18,7 +18,7 @@
</adf-no-content-template>
<data-columns>
<data-column key="icon" type="image" srTitle="ADF_TASK_LIST.PROPERTIES.THUMBNAIL" [sortable]="false"></data-column>
<data-column key="icon" type="image" [sr-title]="'ADF_TASK_LIST.PROPERTIES.THUMBNAIL'" [sortable]="false"></data-column>
<data-column key="name" type="text" title="ADF_TASK_LIST.PROPERTIES.NAME" class="adf-full-width adf-ellipsis-cell" [sortable]="true"></data-column>
<data-column key="created" type="date" format="shortDate" title="ADF_TASK_LIST.PROPERTIES.CREATED"></data-column>
</data-columns>

View File

@@ -95,10 +95,6 @@ export class TaskAttachmentListComponent implements OnChanges, AfterContentInit
this.attachments = [];
}
hasCustomEmptyTemplate() {
return !!this.emptyTemplate;
}
reload(): void {
this.ngZone.run(() => {
this.loadAttachmentsByTaskId(this.taskId);
@@ -130,10 +126,6 @@ export class TaskAttachmentListComponent implements OnChanges, AfterContentInit
}
}
isEmpty(): boolean {
return this.attachments && this.attachments.length === 0;
}
onShowRowActionsMenu(event: any) {
const viewAction = {
title: 'ADF_TASK_LIST.MENU_ACTIONS.VIEW_CONTENT',
@@ -195,10 +187,6 @@ export class TaskAttachmentListComponent implements OnChanges, AfterContentInit
);
}
isDisabled(): boolean {
return this.disabled;
}
private loadAttachmentsByTaskId(taskId: string) {
if (taskId) {
this.isLoading = true;

View File

@@ -1,66 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { UserRepresentation } from '@alfresco/js-api';
export class BpmUserModel implements UserRepresentation {
apps: any;
capabilities: string[];
company: string;
created: Date;
email: string;
externalId: string;
firstName: string;
lastName: string;
fullname: string;
groups: any;
id: number;
lastUpdate: Date;
latestSyncTimeStamp: Date;
password: string;
pictureId: number;
status: string;
tenantId: number;
tenantName: string;
tenantPictureId: number;
type: string;
constructor(input?: any) {
if (input) {
this.apps = input.apps;
this.capabilities = input.capabilities;
this.company = input.company;
this.created = input.created;
this.email = input.email;
this.externalId = input.externalId;
this.firstName = input.firstName;
this.lastName = input.lastName;
this.fullname = input.fullname;
this.groups = input.groups;
this.id = input.id;
this.lastUpdate = input.lastUpdate;
this.latestSyncTimeStamp = input.latestSyncTimeStamp;
this.password = input.password;
this.pictureId = input.pictureId;
this.status = input.status;
this.tenantId = input.tenantId;
this.tenantName = input.tenantName;
this.tenantPictureId = input.tenantPictureId;
this.type = input.type;
}
}
}

View File

@@ -1,45 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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.
*/
/**
* This object represent the process service user.*
*/
import { LightUserRepresentation } from '@alfresco/js-api';
export class UserProcessModel implements LightUserRepresentation {
id?: number;
email?: string;
firstName?: string;
lastName?: string;
pictureId?: number;
externalId?: string;
userImage?: string;
constructor(input?: any) {
if (input) {
this.id = input.id;
this.email = input.email || null;
this.firstName = input.firstName || null;
this.lastName = input.lastName || null;
this.pictureId = input.pictureId || null;
this.externalId = input.externalId || null;
this.userImage = input.userImage;
}
}
}

View File

@@ -16,6 +16,3 @@
*/
export * from './services/people-process.service';
export * from './models/bpm-user.model';
export * from './models/user-process.model';

View File

@@ -16,27 +16,27 @@
*/
import { fakeAsync, TestBed } from '@angular/core/testing';
import { UserProcessModel } from '../models/user-process.model';
import { PeopleProcessService } from './people-process.service';
import { CoreTestingModule } from '@alfresco/adf-core';
import { LightUserRepresentation } from '@alfresco/js-api';
declare let jasmine: any;
const firstInvolvedUser: UserProcessModel = new UserProcessModel({
const firstInvolvedUser: LightUserRepresentation = {
id: 1,
email: 'fake-user1@fake.com',
firstName: 'fakeName1',
lastName: 'fakeLast1'
});
};
const secondInvolvedUser: UserProcessModel = new UserProcessModel({
const secondInvolvedUser: LightUserRepresentation = {
id: 2,
email: 'fake-user2@fake.com',
firstName: 'fakeName2',
lastName: 'fakeLast2'
});
};
const fakeInvolveUserList: UserProcessModel[] = [firstInvolvedUser, secondInvolvedUser];
const fakeInvolveUserList: LightUserRepresentation[] = [firstInvolvedUser, secondInvolvedUser];
const errorResponse = { error: new Error('Unsuccessful HTTP response') };
@@ -60,7 +60,7 @@ describe('PeopleProcessService', () => {
});
it('should be able to retrieve people to involve in the task', fakeAsync(() => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users: UserProcessModel[]) => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users) => {
expect(users).toBeDefined();
expect(users.length).toBe(2);
expect(users[0].id).toEqual(1);
@@ -77,11 +77,11 @@ describe('PeopleProcessService', () => {
}));
it('should be able to get people images for people retrieved', fakeAsync(() => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users: UserProcessModel[]) => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users) => {
expect(users).toBeDefined();
expect(users.length).toBe(2);
expect(service.getUserImage(users[0])).toContain('/users/' + users[0].id + '/picture');
expect(service.getUserImage(users[1])).toContain('/users/' + users[1].id + '/picture');
expect(service.getUserImage(users[0].id.toString())).toContain('/users/' + users[0].id + '/picture');
expect(service.getUserImage(users[1].id.toString())).toContain('/users/' + users[1].id + '/picture');
});
jasmine.Ajax.requests.mostRecent().respondWith({
@@ -92,13 +92,13 @@ describe('PeopleProcessService', () => {
}));
it('should return user image url', () => {
const url = service.getUserImage(firstInvolvedUser);
const url = service.getUserImage(firstInvolvedUser.id.toString());
expect(url).toContain('/users/' + firstInvolvedUser.id + '/picture');
});
it('should return empty list when there are no users to involve', fakeAsync(() => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users: UserProcessModel[]) => {
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe((users) => {
expect(users).toBeDefined();
expect(users.length).toBe(0);
});

View File

@@ -16,12 +16,10 @@
*/
import { Injectable } from '@angular/core';
import { Observable, from, of } from 'rxjs';
import { Observable, from } from 'rxjs';
import { AlfrescoApiService, GroupModel } from '@alfresco/adf-core';
import { BpmUserModel } from '../models/bpm-user.model';
import { UserProcessModel } from '../models/user-process.model';
import { combineAll, defaultIfEmpty, map, switchMap } from 'rxjs/operators';
import { TaskActionsApi, UsersApi, ResultListDataRepresentationLightUserRepresentation, ActivitiGroupsApi, UserProfileApi } from '@alfresco/js-api';
import { map } from 'rxjs/operators';
import { TaskActionsApi, UsersApi, ActivitiGroupsApi, UserProfileApi, UserRepresentation, LightUserRepresentation } from '@alfresco/js-api';
@Injectable({
providedIn: 'root'
@@ -58,8 +56,8 @@ export class PeopleProcessService {
*
* @returns User information object
*/
getCurrentUserInfo(): Observable<BpmUserModel> {
return from(this.profileApi.getProfile()).pipe(map((userRepresentation) => new BpmUserModel(userRepresentation)));
getCurrentUserInfo(): Observable<UserRepresentation> {
return from(this.profileApi.getProfile());
}
/**
@@ -94,27 +92,19 @@ export class PeopleProcessService {
* @param groupId group id
* @returns Array of user information objects
*/
getWorkflowUsers(taskId?: string, searchWord?: string, groupId?: string): Observable<UserProcessModel[]> {
getWorkflowUsers(taskId?: string, searchWord?: string, groupId?: number): Observable<LightUserRepresentation[]> {
const option = { excludeTaskId: taskId, filter: searchWord, groupId };
return from(this.getWorkflowUserApi(option)).pipe(
switchMap((response) => (response.data as UserProcessModel[]) || []),
map((user) => {
user.userImage = this.getUserProfileImageApi(user.id.toString());
return of(user);
}),
combineAll(),
defaultIfEmpty([])
);
return from(this.userApi.getUsers(option)).pipe(map((response) => response.data || []));
}
/**
* Gets the profile picture URL for the specified user.
*
* @param user The target user
* @param userId The target user
* @returns Profile picture URL
*/
getUserImage(user: UserProcessModel): string {
return this.getUserProfileImageApi(user.id.toString());
getUserImage(userId: string): string {
return this.userApi.getUserProfilePictureUrl(userId);
}
/**
@@ -124,8 +114,8 @@ export class PeopleProcessService {
* @param idToInvolve ID of the user to involve
* @returns Empty response when the update completes
*/
involveUserWithTask(taskId: string, idToInvolve: string): Observable<UserProcessModel[]> {
return from(this.involveUserToTaskApi(taskId, { userId: idToInvolve }));
involveUserWithTask(taskId: string, idToInvolve: string): Observable<LightUserRepresentation[]> {
return from(this.taskActionsApi.involveUser(taskId, { userId: idToInvolve }));
}
/**
@@ -135,23 +125,7 @@ export class PeopleProcessService {
* @param idToRemove ID of the user to remove
* @returns Empty response when the update completes
*/
removeInvolvedUser(taskId: string, idToRemove: string): Observable<UserProcessModel[]> {
return from(this.removeInvolvedUserFromTaskApi(taskId, { userId: idToRemove }));
}
private getWorkflowUserApi(options: any): Promise<ResultListDataRepresentationLightUserRepresentation> {
return this.userApi.getUsers(options);
}
private involveUserToTaskApi(taskId: string, node: any) {
return this.taskActionsApi.involveUser(taskId, node);
}
private removeInvolvedUserFromTaskApi(taskId: string, node: any) {
return this.taskActionsApi.removeInvolvedUser(taskId, node);
}
private getUserProfileImageApi(userId: string): string {
return this.userApi.getUserProfilePictureUrl(userId);
removeInvolvedUser(taskId: string, idToRemove: string): Observable<LightUserRepresentation[]> {
return from(this.taskActionsApi.removeInvolvedUser(taskId, { userId: idToRemove }));
}
}

View File

@@ -0,0 +1,48 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 {
AppDefinitionRepresentation,
LightUserRepresentation,
ProcessInstanceRepresentation,
RestVariable,
UserRepresentation,
UserTaskFilterRepresentation
} from '@alfresco/js-api';
/** @deprecated use js-api/ProcessInstanceRepresentation instead */
export type ProcessInstance = ProcessInstanceRepresentation;
/** @deprecated use js-api/UserTaskFilterRepresentation instead */
export type FilterRepresentationModel = UserTaskFilterRepresentation;
/** @deprecated use js-api/UserTaskFilterRepresentation instead */
export type FilterParamsModel = UserTaskFilterRepresentation;
/** @deprecated use js-api/UserRepresentation instead */
export type BpmUserModel = UserRepresentation;
/** @deprecated use js-api/AppDefinitionRepresentation instead */
export type AppDefinitionRepresentationModel = AppDefinitionRepresentation;
/** @deprecated use js-api/LightUserRepresentation instead */
export type UserProcessModel = LightUserRepresentation;
/** @deprecated use js-api/RestVariable instead */
export type ProcessInstanceVariable = RestVariable;
export { ProcessDefinitionRepresentation } from '@alfresco/js-api';

View File

@@ -17,7 +17,7 @@
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { CoreModule, FormatSpacePipe } from '@alfresco/adf-core';
import { CoreModule, FormatSpacePipe, InitialUsernamePipe } from '@alfresco/adf-core';
import { FormComponent } from './form.component';
import { StartFormComponent } from './start-form.component';
import { FormCustomOutcomesComponent } from './form-custom-outcomes.component';
@@ -35,7 +35,7 @@ import { FileViewerWidgetComponent } from './widgets/file-viewer/file-viewer.wid
import { AlfrescoViewerModule } from '@alfresco/adf-content-services';
@NgModule({
imports: [DynamicTableModule, CoreModule, AlfrescoViewerModule, MaterialModule, FormatSpacePipe],
imports: [DynamicTableModule, CoreModule, AlfrescoViewerModule, MaterialModule, FormatSpacePipe, InitialUsernamePipe],
declarations: [
UploadWidgetComponent,
FormComponent,

View File

@@ -102,7 +102,7 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn
}
loadStartForm(processId: string) {
this.processService.getProcess(processId).subscribe((instance: any) => {
this.processService.getProcess(processId).subscribe((instance) => {
this.processService.getStartFormInstance(processId).subscribe(
(form) => {
this.formName = form.name;

View File

@@ -26,7 +26,7 @@
<div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div>
<div *ngIf="user.pictureId" class="adf-people-widget-image-row">
<img id="adf-people-widget-pic-{{i}}" class="adf-people-widget-image"
[alt]="getDisplayName(user)" [src]="peopleProcessService.getUserImage(user)"/>
[alt]="getDisplayName(user)" [src]="peopleProcessService.getUserImage(user.id.toString())"/>
</div>
<span class="adf-people-label-name">{{getDisplayName(user)}}</span>
</div>

View File

@@ -22,7 +22,7 @@ import { Observable, of } from 'rxjs';
import { PeopleWidgetComponent } from './people.widget';
import { TranslateService } from '@ngx-translate/core';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
describe('PeopleWidgetComponent', () => {
let widget: PeopleWidgetComponent;
@@ -53,29 +53,29 @@ describe('PeopleWidgetComponent', () => {
});
it('should return full name for a given model', () => {
const model = new UserProcessModel({
const model = {
firstName: 'John',
lastName: 'Doe'
});
};
expect(widget.getDisplayName(model)).toBe('John Doe');
});
it('should skip first name for display name', () => {
const model = new UserProcessModel({ firstName: null, lastName: 'Doe' });
const model = { firstName: null, lastName: 'Doe' };
expect(widget.getDisplayName(model)).toBe('Doe');
});
it('should skip last name for display name', () => {
const model = new UserProcessModel({ firstName: 'John', lastName: null });
const model = { firstName: 'John', lastName: null };
expect(widget.getDisplayName(model)).toBe('John');
});
it('should init value from the field', async () => {
widget.field.value = new UserProcessModel({
widget.field.value = {
id: 'people-id',
firstName: 'John',
lastName: 'Doe'
});
};
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of(null));
@@ -87,11 +87,11 @@ describe('PeopleWidgetComponent', () => {
});
it('should show the readonly value when the form is readonly', async () => {
widget.field.value = new UserProcessModel({
widget.field.value = {
id: 'people-id',
firstName: 'John',
lastName: 'Doe'
});
};
widget.field.readOnly = true;
widget.field.form.readOnly = true;
@@ -131,12 +131,12 @@ describe('PeopleWidgetComponent', () => {
})
);
widget.field.value = new UserProcessModel({
widget.field.value = {
id: 'people-id',
firstName: 'John',
lastName: 'Doe',
email: 'john@test.com'
});
};
widget.ngOnInit();
const involvedUser = fixture.debugElement.nativeElement.querySelector('input[data-automation-id="adf-people-search-input"]');
@@ -179,7 +179,7 @@ describe('PeopleWidgetComponent', () => {
});
describe('when template is ready', () => {
const fakeUserResult = [
const fakeUserResult: LightUserRepresentation[] = [
{ id: 1001, firstName: 'Test01', lastName: 'Test01', email: 'test' },
{ id: 1002, firstName: 'Test02', lastName: 'Test02', email: 'test2' }
];

View File

@@ -22,8 +22,8 @@ import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild, ViewEnc
import { UntypedFormControl } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { LightUserRepresentation } from '@alfresco/js-api';
@Component({
selector: 'people-widget',
@@ -50,7 +50,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
@Output()
peopleSelected: EventEmitter<number> = new EventEmitter();
groupId: string;
groupId: number;
value: any;
searchTerm = new UntypedFormControl();
@@ -67,7 +67,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
const value = searchTerm.email ? this.getDisplayName(searchTerm) : searchTerm;
return this.peopleProcessService.getWorkflowUsers(undefined, value, this.groupId).pipe(catchError(() => of([])));
}),
map((list: UserProcessModel[]) => {
map((list) => {
const value = this.searchTerm.value.email ? this.getDisplayName(this.searchTerm.value) : this.searchTerm.value;
this.checkUserAndValidateForm(list, value);
return list;
@@ -94,7 +94,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
}
}
checkUserAndValidateForm(list: UserProcessModel[], value: string): void {
checkUserAndValidateForm(list: LightUserRepresentation[], value: string): void {
const isValidUser = this.isValidUser(list, value);
if (isValidUser || value === '') {
this.field.validationSummary.message = '';
@@ -107,7 +107,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
}
}
isValidUser(users: UserProcessModel[], name: string): boolean {
isValidUser(users: LightUserRepresentation[], name: string): boolean {
if (users) {
return !!users.find((user) => {
const selectedUser = this.getDisplayName(user).toLocaleLowerCase() === name.toLocaleLowerCase();
@@ -120,7 +120,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
return false;
}
getDisplayName(model: UserProcessModel) {
getDisplayName(model: LightUserRepresentation) {
if (model) {
const displayName = `${model.firstName || ''} ${model.lastName || ''}`;
return displayName.trim();
@@ -128,7 +128,7 @@ export class PeopleWidgetComponent extends WidgetComponent implements OnInit {
return '';
}
onItemSelect(item?: UserProcessModel) {
onItemSelect(item?: LightUserRepresentation) {
if (item) {
this.field.value = item;
} else {

View File

@@ -15,65 +15,80 @@
* limitations under the License.
*/
import { AppDefinitionRepresentationModel } from '../task-list';
import { AppDefinitionRepresentation } from '@alfresco/js-api';
export const nonDeployedApps = [new AppDefinitionRepresentationModel({
id: '1',
name: '1',
icon: 'icon1'
}), new AppDefinitionRepresentationModel({
id: '1',
name: '2',
icon: 'icon2'
}), new AppDefinitionRepresentationModel({
id: '1',
name: '3',
icon: 'icon3'
})];
export const deployedApps = [new AppDefinitionRepresentationModel({
id: 1,
name: 'App1',
icon: 'icon1',
deploymentId: '1',
defaultAppId: 'fake-app-1',
modelId: null,
tenantId: null
}), new AppDefinitionRepresentationModel({
id: 2,
name: 'App2',
icon: 'icon2',
deploymentId: '2',
modelId: null,
tenantId: null
}), new AppDefinitionRepresentationModel({
id: 3,
name: 'App3',
icon: 'icon3',
deploymentId: '3',
modelId: null,
tenantId: null
}), new AppDefinitionRepresentationModel({
id: 4,
name: 'App4',
icon: 'icon4',
deploymentId: '4',
modelId: 65,
tenantId: null
}), new AppDefinitionRepresentationModel({
id: 5,
name: 'App5',
icon: 'icon5',
deploymentId: '5',
modelId: 66,
tenantId: 9
}), new AppDefinitionRepresentationModel({
id: 6,
name: 'App6',
icon: 'icon6',
deploymentId: '6',
tenantId: 9,
modelId: 66
})];
export const defaultApp = [new AppDefinitionRepresentationModel({
defaultAppId: 'tasks'
})];
export const nonDeployedApps: AppDefinitionRepresentation[] = [
{
id: 1,
name: '1',
icon: 'icon1'
},
{
id: 1,
name: '2',
icon: 'icon2'
},
{
id: 1,
name: '3',
icon: 'icon3'
}
];
export const deployedApps: AppDefinitionRepresentation[] = [
{
id: 1,
name: 'App1',
icon: 'icon1',
deploymentId: '1',
defaultAppId: 'fake-app-1',
modelId: null,
tenantId: null
},
{
id: 2,
name: 'App2',
icon: 'icon2',
deploymentId: '2',
modelId: null,
tenantId: null
},
{
id: 3,
name: 'App3',
icon: 'icon3',
deploymentId: '3',
modelId: null,
tenantId: null
},
{
id: 4,
name: 'App4',
icon: 'icon4',
deploymentId: '4',
modelId: 65,
tenantId: null
},
{
id: 5,
name: 'App5',
icon: 'icon5',
deploymentId: '5',
modelId: 66,
tenantId: 9
},
{
id: 6,
name: 'App6',
icon: 'icon6',
deploymentId: '6',
tenantId: 9,
modelId: 66
}
];
export const defaultApp: AppDefinitionRepresentation[] = [
{
defaultAppId: 'tasks'
}
];

View File

@@ -1,99 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { FilterProcessRepresentationModel } from '../../process-list/models/filter-process.model';
export const fakeProcessFilters = [
new FilterProcessRepresentationModel({
id: 10,
name: 'FakeCompleted',
icon: 'glyphicon-th',
filter: { state: 'open', assignment: 'fake-involved' }
}),
new FilterProcessRepresentationModel({
id: 20,
name: 'FakeAll',
icon: 'glyphicon-random',
filter: { state: 'open', assignment: 'fake-assignee' }
}),
new FilterProcessRepresentationModel({
id: 30,
name: 'Running',
icon: 'glyphicon-ok-sign',
filter: { state: 'open', assignment: 'fake-running' }
})
];
export const fakeProcessFiltersResponse = {
size: 1,
total: 1,
start: 0,
data: [
new FilterProcessRepresentationModel({
name: 'Running',
appId: '22',
id: 333,
recent: true,
icon: 'glyphicon-random',
filter: { sort: 'created-desc', name: '', state: 'running' }
})
]
};
export const dummyRunningFilter = {
appId: 123,
name: 'Running',
filter: { sort: 'created-desc', name: '', state: 'running' },
icon: 'fa-random',
id: 18,
index: 10,
recent: false,
hasFilter: () => true
};
export const dummyCompletedFilter = {
appId: 123,
name: 'Completed',
filter: { sort: 'created-desc', name: '', state: 'completed' },
icon: 'fa-random',
id: 19,
index: 11,
recent: false,
hasFilter: () => true
};
export const dummyAllFilter = {
appId: 123,
name: 'All',
filter: { sort: 'created-desc', name: '', state: 'all' },
icon: 'fa-random',
id: 20,
index: 12,
recent: false,
hasFilter: () => true
};
export const dummyDuplicateRunningFilter = {
appId: 123,
name: 'Running',
filter: { sort: 'created-desc', name: '', state: 'running' },
icon: 'fa-random',
id: 21,
index: 13,
recent: false,
hasFilter: () => true
};

View File

@@ -15,9 +15,9 @@
* limitations under the License.
*/
import { ProcessListModel } from '../../process-list/models/process-list.model';
import { ResultListDataRepresentationProcessInstanceRepresentation } from '@alfresco/js-api';
export const fakeProcessInstance = new ProcessListModel({
export const fakeProcessInstance: ResultListDataRepresentationProcessInstanceRepresentation = {
size: 2,
total: 2,
start: 0,
@@ -28,7 +28,7 @@ export const fakeProcessInstance = new ProcessListModel({
businessKey: null,
processDefinitionId: 'fakeprocess:5:7507',
tenantId: 'tenant_1',
started: '2015-11-09T12:36:14.184+0000',
started: new Date('2015-11-09T12:36:14.184+0000'),
ended: null,
startedBy: {
id: 3,
@@ -58,7 +58,7 @@ export const fakeProcessInstance = new ProcessListModel({
businessKey: null,
processDefinitionId: 'fakeprocess:5:7507',
tenantId: 'tenant_1',
started: '2018-01-10T17:02:22.597+0000',
started: new Date('2018-01-10T17:02:22.597+0000'),
ended: null,
startedBy: {
id: 3,
@@ -83,7 +83,7 @@ export const fakeProcessInstance = new ProcessListModel({
]
}
]
});
};
export const fakeProcessInstancesWithNoName = {
size: 2,
@@ -125,12 +125,12 @@ export const fakeProcessInstancesWithNoName = {
]
};
export const fakeProcessInstancesEmpty = new ProcessListModel({
export const fakeProcessInstancesEmpty: ResultListDataRepresentationProcessInstanceRepresentation = {
size: 0,
total: 0,
start: 0,
data: []
});
};
export const fakeProcessCustomSchema = [
{

View File

@@ -16,37 +16,37 @@
*/
/* spell-checker: disable */
import { ProcessInstance } from '../../process-list/models/process-instance.model';
import { ProcessInstanceRepresentation } from '@alfresco/js-api';
export const exampleProcess = new ProcessInstance({
export const exampleProcess: ProcessInstanceRepresentation = {
id: '123',
name: 'Process 123',
started: '2016-11-10T03:37:30.010+0000',
started: new Date('2016-11-10T03:37:30.010+0000'),
startedBy: {
id: 1001,
firstName: 'Bob',
lastName: 'Jones',
email: 'bob@app.activiti.com'
}
});
};
export const processEnded = new ProcessInstance({
export const processEnded: ProcessInstanceRepresentation = {
id: '123',
name: 'Process 123',
started: '2016-11-10T03:37:30.010+0000',
started: new Date('2016-11-10T03:37:30.010+0000'),
startedBy: {
id: 1001,
firstName: 'Bob',
lastName: 'Jones',
email: 'bob@app.activiti.com'
},
ended: '2016-11-11T03:37:30.010+0000'
});
ended: new Date('2016-11-11T03:37:30.010+0000')
};
export const mockRunningProcess = new ProcessInstance({
export const mockRunningProcess: ProcessInstanceRepresentation = {
id: '123',
name: 'Process 123',
started: '2016-11-10T03:37:30.010+0000',
started: new Date('2016-11-10T03:37:30.010+0000'),
startedBy: {
id: 1001,
firstName: 'Bob',
@@ -54,12 +54,12 @@ export const mockRunningProcess = new ProcessInstance({
email: 'bob@app.activiti.com'
},
ended: null
});
};
export const exampleProcessNoName = new ProcessInstance({
export const exampleProcessNoName: ProcessInstanceRepresentation = {
id: '123',
name: null,
started: '2016-11-10T03:37:30.010+0000',
started: new Date('2016-11-10T03:37:30.010+0000'),
startedBy: {
id: 1001,
firstName: 'Bob',
@@ -67,4 +67,4 @@ export const exampleProcessNoName = new ProcessInstance({
email: 'bob@app.activiti.com'
},
processDefinitionName: 'My Process'
});
};

View File

@@ -1,47 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { TaskDetailsModel } from '../../task-list';
import { ProcessDefinitionRepresentation } from '../../process-list/models/process-definition.model';
export const mockError = {
message: null,
messageKey: 'GENERAL.ERROR.FORBIDDEN'
};
export const fakeTasksList = {
data: [
new TaskDetailsModel({
id: 1,
name: 'Task 1',
processInstanceId: 1000,
created: '2016-11-10T03:37:30.010+0000'
}),
new TaskDetailsModel({
id: 2,
name: 'Task 2',
processInstanceId: 1000,
created: '2016-11-10T03:37:30.010+0000'
})
]
};
export const fakeProcessDef = new ProcessDefinitionRepresentation({
id: '32323',
key: 'blah',
name: 'Process 1'
});

View File

@@ -15,38 +15,44 @@
* limitations under the License.
*/
import { ProcessDefinitionRepresentation } from '../../process-list/models/process-definition.model';
import { ProcessInstance } from '../../process-list/models/process-instance.model';
import { ProcessInstanceRepresentation, ProcessDefinitionRepresentation } from '@alfresco/js-api';
export const newProcess = new ProcessInstance({
export const newProcess: ProcessInstanceRepresentation = {
id: '32323',
name: 'Process'
});
};
export const testProcessDef = new ProcessDefinitionRepresentation({
export const testProcessDef: ProcessDefinitionRepresentation = {
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
});
};
export const testProcessDefinitions = [new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
})];
export const testProcessDefinitions: ProcessDefinitionRepresentation[] = [
{
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
}
];
export const testMultipleProcessDefs = [new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
}), new ProcessDefinitionRepresentation({
id: 'my:process2',
name: 'My Process 2',
hasStartForm: true
})];
export const testMultipleProcessDefs: ProcessDefinitionRepresentation[] = [
{
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
},
{
id: 'my:process2',
name: 'My Process 2',
hasStartForm: true
}
];
export const testProcessDefWithForm = [new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: true
})];
export const testProcessDefWithForm: ProcessDefinitionRepresentation[] = [
{
id: 'my:process1',
name: 'My Process 1',
hasStartForm: true
}
];

View File

@@ -16,13 +16,9 @@
*/
export * from './process/process-instances-list.mock';
export * from './process/process.service.mock';
export * from './process/start-process.component.mock';
export * from './process/process.model.mock';
export * from './process/process-comments.mock';
export * from './task/task-details.mock';
export * from './task/task-list.mock';
export * from './task/tasklist-service.mock';
export * from './process/process-filters.mock';
export * from './task/task-filters.mock';

View File

@@ -1,178 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../../task-list/models/filter.model';
export const fakeFiltersResponse: any = {
size: 2,
total: 2,
start: 0,
data: [
{
id: 1,
name: 'FakeInvolvedTasks',
recent: false,
icon: 'glyphicon-align-left',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
},
{
id: 2,
name: 'FakeMyTasks',
recent: false,
icon: 'glyphicon-align-left',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-assignee' }
}
]
};
export const fakeTaskFilters = [
new FilterRepresentationModel({
name: 'FakeInvolvedTasks',
icon: 'glyphicon-align-left',
id: 10,
filter: { state: 'open', assignment: 'fake-involved' }
}),
new FilterRepresentationModel({
name: 'FakeMyTasks1',
icon: 'glyphicon-ok-sign',
id: 11,
filter: { state: 'open', assignment: 'fake-assignee' }
}),
new FilterRepresentationModel({
name: 'FakeMyTasks2',
icon: 'glyphicon-inbox',
id: 12,
filter: { state: 'open', assignment: 'fake-assignee' }
})
];
export const fakeAppFilter = {
size: 1,
total: 1,
start: 0,
data: [
{
id: 1,
name: 'FakeInvolvedTasks',
recent: false,
icon: 'glyphicon-align-left',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' }
}
]
};
export const fakeFilter: TaskQueryRequestRepresentationModel = {
sort: 'created-desc',
text: '',
state: 'open',
assignment: 'fake-assignee'
};
export const mockFilterNoState: TaskQueryRequestRepresentationModel = {
sort: 'created-desc',
text: '',
assignment: 'fake-assignee'
};
export const fakeRepresentationFilter1: FilterRepresentationModel = new FilterRepresentationModel({
appId: 1,
name: 'CONTAIN FILTER',
recent: true,
icon: 'glyphicon-align-left',
filter: {
processDefinitionId: null,
processDefinitionKey: null,
name: null,
state: 'open',
sort: 'created-desc',
assignment: 'involved',
dueAfter: null,
dueBefore: null
}
});
export const fakeRepresentationFilter2: FilterRepresentationModel = new FilterRepresentationModel({
appId: 2,
name: 'NO TASK FILTER',
recent: false,
icon: 'glyphicon-inbox',
filter: {
processDefinitionId: null,
processDefinitionKey: null,
name: null,
state: 'open',
sort: 'created-desc',
assignment: 'assignee',
dueAfter: null,
dueBefore: null
}
});
export const dummyMyTasksFilter = {
appId: 101,
name: 'My Tasks',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-mytasks' },
icon: 'fa-random',
id: 81,
index: 21,
recent: false,
hasFilter: () => true
};
export const dummyInvolvedTasksFilter = {
appId: 101,
name: 'Involved Tasks',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-involved' },
icon: 'fa-random',
id: 82,
index: 22,
recent: false,
hasFilter: () => true
};
export const dummyQueuedTasksFilter = {
appId: 101,
name: 'Queued Tasks',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-queued' },
icon: 'fa-random',
id: 83,
index: 23,
recent: false,
hasFilter: () => true
};
export const dummyCompletedTasksFilter = {
appId: 101,
name: 'Completed',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-completed' },
icon: 'fa-random',
id: 84,
index: 24,
recent: false,
hasFilter: () => true
};
export const dummyDuplicateMyTasksFilter = {
appId: 101,
name: 'My Tasks',
filter: { sort: 'created-desc', name: '', state: 'open', assignment: 'fake-mytasks' },
icon: 'fa-random',
id: 85,
index: 25,
recent: false,
hasFilter: () => true
};

View File

@@ -15,9 +15,7 @@
* limitations under the License.
*/
import { TaskListModel } from '../../task-list/models/task-list.model';
export const fakeGlobalTask = new TaskListModel({
export const fakeGlobalTask: any = {
size: 2,
start: 0,
total: 2,
@@ -76,7 +74,7 @@ export const fakeGlobalTask = new TaskListModel({
endDate: null
}
]
});
};
export const fakeCustomSchema = [
{
@@ -126,164 +124,170 @@ export const fakeEmptyTask = {
data: []
};
export const paginatedTask = new TaskListModel({
export const paginatedTask: any = {
size: 5,
total: 9,
start: 0,
data: [{
id: '69211',
name: 'My Task Name',
description: '',
category: null,
assignee: {id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com'},
created: '2020-02-06T18:41:21.587+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}, {
id: '61054',
name: null,
description: null,
category: null,
assignee: {id: 19, firstName: 'Mm9ntWGB', lastName: 'jfQOzSDL', email: 'c4jly@activiti.test.com'},
created: '2020-02-06T15:26:32.488+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: '61049',
processInstanceName: null,
processDefinitionId: 'upload:1:50118',
processDefinitionName: 'upload',
processDefinitionDescription: null,
processDefinitionKey: 'upload',
processDefinitionCategory: 'http://www.activiti.org/processdef',
processDefinitionVersion: 1,
processDefinitionDeploymentId: '50115',
formKey: '8474',
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: 'sid-7A12380E-28F8-4B15-9326-C5CFB8DD5BBC',
executionId: '61049',
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}, {
id: '61048',
name: 'My Task Name',
description: null,
category: null,
assignee: {id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com'},
created: '2020-02-06T15:26:03.012+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}, {
id: '54705',
name: 'My Task Name',
description: '',
category: '1349',
assignee: {id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com'},
created: '2020-02-06T13:01:23.403+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}, {
id: '50158',
name: 'My Task Name',
description: '',
category: '1349',
assignee: {id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com'},
created: '2020-02-06T09:13:55.532+0000',
dueDate: '2019-01-09T11:53:00.000+0000',
endDate: null,
duration: null,
priority: 0,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: '8484',
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}]
});
data: [
{
id: '69211',
name: 'My Task Name',
description: '',
category: null,
assignee: { id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com' },
created: '2020-02-06T18:41:21.587+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
},
{
id: '61054',
name: null,
description: null,
category: null,
assignee: { id: 19, firstName: 'Mm9ntWGB', lastName: 'jfQOzSDL', email: 'c4jly@activiti.test.com' },
created: '2020-02-06T15:26:32.488+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: '61049',
processInstanceName: null,
processDefinitionId: 'upload:1:50118',
processDefinitionName: 'upload',
processDefinitionDescription: null,
processDefinitionKey: 'upload',
processDefinitionCategory: 'http://www.activiti.org/processdef',
processDefinitionVersion: 1,
processDefinitionDeploymentId: '50115',
formKey: '8474',
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: 'sid-7A12380E-28F8-4B15-9326-C5CFB8DD5BBC',
executionId: '61049',
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
},
{
id: '61048',
name: 'My Task Name',
description: null,
category: null,
assignee: { id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com' },
created: '2020-02-06T15:26:03.012+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
},
{
id: '54705',
name: 'My Task Name',
description: '',
category: '1349',
assignee: { id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com' },
created: '2020-02-06T13:01:23.403+0000',
dueDate: null,
endDate: null,
duration: null,
priority: 50,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: null,
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
},
{
id: '50158',
name: 'My Task Name',
description: '',
category: '1349',
assignee: { id: 1493, firstName: 'fake name', lastName: 'fake', email: 'abc@test.com' },
created: '2020-02-06T09:13:55.532+0000',
dueDate: '2019-01-09T11:53:00.000+0000',
endDate: null,
duration: null,
priority: 0,
parentTaskId: null,
parentTaskName: null,
processInstanceId: null,
processInstanceName: null,
processDefinitionId: null,
processDefinitionName: null,
processDefinitionDescription: null,
processDefinitionKey: null,
processDefinitionCategory: null,
processDefinitionVersion: 0,
processDefinitionDeploymentId: null,
formKey: '8484',
processInstanceStartUserId: null,
initiatorCanCompleteTask: false,
adhocTaskCanBeReassigned: false,
taskDefinitionKey: null,
executionId: null,
memberOfCandidateGroup: false,
memberOfCandidateUsers: false,
managerOfCandidateGroup: false
}
]
};

View File

@@ -1,38 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { TaskListModel } from '../../task-list/models/task-list.model';
import { fakeAppFilter } from './task-filters.mock';
export const fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' };
export const fakeTaskList = new TaskListModel({
size: 1,
total: 1,
start: 0,
data: [
{
id: '1',
name: 'FakeNameTask',
description: null,
category: null,
assignee: fakeUser1,
created: '2016-07-15T11:19:17.440+0000'
}
]
});
export const fakeAppPromise = Promise.resolve(fakeAppFilter);

View File

@@ -20,14 +20,14 @@ import { DataRowActionEvent, DataRowEvent, ObjectDataRow } from '@alfresco/adf-c
import { UserEventModel } from '../../../task-list/models/user-event.model';
import { PeopleListComponent } from './people-list.component';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
const fakeUser: UserProcessModel = new UserProcessModel({
const fakeUser: LightUserRepresentation = {
id: 1,
firstName: 'fake-name',
lastName: 'fake-last',
email: 'fake@mail.com'
});
};
describe('PeopleListComponent', () => {
let peopleListComponent: PeopleListComponent;
@@ -35,7 +35,7 @@ describe('PeopleListComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessTestingModule]
imports: [ProcessTestingModule, PeopleListComponent]
});
fixture = TestBed.createComponent(PeopleListComponent);
peopleListComponent = fixture.componentInstance;

View File

@@ -15,19 +15,20 @@
* limitations under the License.
*/
import { DataTableComponent, DataCellEvent, DataColumnListComponent, ShowHeaderMode } from '@alfresco/adf-core';
import { DataTableComponent, DataCellEvent, DataColumnListComponent, ShowHeaderMode, DataTableModule } from '@alfresco/adf-core';
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { UserEventModel } from '../../../task-list/models/user-event.model';
import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
@Component({
selector: 'adf-people-list',
standalone: true,
imports: [CommonModule, DataTableModule],
templateUrl: './people-list.component.html',
styleUrls: ['./people-list.component.scss']
})
export class PeopleListComponent implements AfterContentInit {
@ContentChild(DataColumnListComponent)
columnList: DataColumnListComponent;
@@ -36,21 +37,21 @@ export class PeopleListComponent implements AfterContentInit {
/** The array of user data used to populate the people list. */
@Input()
users: UserProcessModel[];
users: LightUserRepresentation[];
/** Toggles whether or not actions should be visible, i.e. the 'Three-Dots' menu. */
/** Toggles if actions should be visible, i.e. the 'Three-Dots' menu. */
@Input()
actions: boolean = false;
/** Emitted when the user clicks a row in the people list. */
@Output()
clickRow = new EventEmitter<UserProcessModel>();
clickRow = new EventEmitter<LightUserRepresentation>();
/** Emitted when the user clicks in the 'Three Dots' drop down menu for a row. */
@Output()
clickAction = new EventEmitter<UserEventModel>();
user: UserProcessModel;
user: LightUserRepresentation;
showHeader = ShowHeaderMode.Never;
ngAfterContentInit() {
@@ -67,20 +68,17 @@ export class PeopleListComponent implements AfterContentInit {
}
onShowRowActionsMenu(event: DataCellEvent) {
const removeAction = {
title: 'Remove',
name: 'remove'
};
event.value.actions = [
removeAction
];
event.value.actions = [removeAction];
}
onExecuteRowAction(event: any) {
const args = event.value;
const action = args.action;
this.clickAction.emit({type: action.name, value: args.row.obj});
this.clickAction.emit({ type: action.name, value: args.row.obj });
}
}

View File

@@ -18,7 +18,7 @@
{{getInitialUserName(entry.row.obj.firstName, entry.row.obj.lastName)}}</div>
<div>
<img [alt]="getDisplayUser(entry.row.obj.firstName, entry.row.obj.lastName, ' ')" *ngIf="entry.row.obj.pictureId" class="adf-people-img"
[src]="peopleProcessService.getUserImage(entry.row.obj)"/>
[src]="peopleProcessService.getUserImage(entry.row.obj.id.toString())"/>
</div>
</ng-template>
</data-column>

View File

@@ -15,18 +15,24 @@
* limitations under the License.
*/
import { TranslationService } from '@alfresco/adf-core';
import { DataTableModule, TranslationService } from '@alfresco/adf-core';
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
import { UntypedFormControl } from '@angular/forms';
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { debounceTime, switchMap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { PerformSearchCallback } from '../../interfaces/perform-search-callback.interface';
import { getDisplayUser } from '../../helpers/get-display-user';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { PeopleListComponent } from '../people-list/people-list.component';
@Component({
selector: 'adf-people-search-field',
standalone: true,
imports: [CommonModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, DataTableModule, PeopleListComponent],
templateUrl: './people-search-field.component.html',
styleUrls: ['./people-search-field.component.scss'],
host: { class: 'adf-people-search-field' },
@@ -40,9 +46,9 @@ export class PeopleSearchFieldComponent {
placeholder: string;
@Output()
rowClick = new EventEmitter<UserProcessModel>();
rowClick = new EventEmitter<LightUserRepresentation>();
users$: Observable<UserProcessModel[]>;
users$: Observable<LightUserRepresentation[]>;
searchUser: UntypedFormControl = new UntypedFormControl();
defaultPlaceholder = 'ADF_TASK_LIST.PEOPLE.SEARCH_USER';
@@ -70,7 +76,7 @@ export class PeopleSearchFieldComponent {
return this.placeholder || this.defaultPlaceholder;
}
onRowClick(model: UserProcessModel) {
onRowClick(model: LightUserRepresentation) {
this.rowClick.emit(model);
}

View File

@@ -1,5 +1,5 @@
<div class="adf-search-text-header">
<ng-content select="[adf-people-search-title], [people-search-title]"></ng-content>
<div class="adf-search-text-header" *ngIf="headerTitle">
{{headerTitle | translate}}
</div>
<adf-people-search-field [performSearch]="performSearch" (rowClick)="onRowClick($event)"></adf-people-search-field>
@@ -9,6 +9,6 @@
{{'ADF_TASK_LIST.PEOPLE.DIALOG_CLOSE' | translate }}
</button>
<button mat-button type="button" id="add-people" (click)="involveUserAndClose()">
<ng-content select="[adf-people-search-action-label], [people-search-action-label]"></ng-content>
{{actionLabel | translate}}
</button>
</div>

View File

@@ -19,21 +19,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { PeopleSearchComponent } from './people-search.component';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
const fakeUser: UserProcessModel = new UserProcessModel({
id: '1',
const fakeUser: LightUserRepresentation = {
id: 1,
firstName: 'John',
lastName: 'Doe',
email: 'JohnDoe@fake.com'
});
};
const fakeSecondUser: UserProcessModel = new UserProcessModel({
id: '2',
const fakeSecondUser: LightUserRepresentation = {
id: 2,
firstName: 'Jane',
lastName: 'Jackson',
email: 'JaneJackson@fake.com'
});
};
describe('PeopleSearchComponent', () => {
let peopleSearchComponent: PeopleSearchComponent;
@@ -44,7 +44,7 @@ describe('PeopleSearchComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessTestingModule]
imports: [ProcessTestingModule, PeopleSearchComponent]
});
fixture = TestBed.createComponent(PeopleSearchComponent);
peopleSearchComponent = fixture.componentInstance;

View File

@@ -15,14 +15,20 @@
* limitations under the License.
*/
import { UserProcessModel } from '../../../common/models/user-process.model';
import { Component, EventEmitter, OnInit, Input, Output, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs';
import { PerformSearchCallback } from '../../interfaces/perform-search-callback.interface';
import { map } from 'rxjs/operators';
import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { PeopleSearchFieldComponent } from '../people-search-field/people-search-field.component';
import { MatButtonModule } from '@angular/material/button';
@Component({
selector: 'adf-people-search',
standalone: true,
imports: [CommonModule, TranslateModule, PeopleSearchFieldComponent, MatButtonModule],
templateUrl: './people-search.component.html',
styleUrls: ['./people-search.component.scss'],
host: {
@@ -30,12 +36,16 @@ import { map } from 'rxjs/operators';
},
encapsulation: ViewEncapsulation.None
})
export class PeopleSearchComponent implements OnInit {
@Input()
headerTitle?: string;
@Input()
actionLabel?: string;
/** Parameters for displaying the list. */
@Input()
results: Observable<UserProcessModel[]>;
results: Observable<LightUserRepresentation[]>;
/** Emitted when a search is performed with a new keyword. */
@Output()
@@ -43,25 +53,22 @@ export class PeopleSearchComponent implements OnInit {
/** Emitted when a user is selected and the action button is clicked. */
@Output()
success = new EventEmitter<UserProcessModel>();
success = new EventEmitter<LightUserRepresentation>();
/** Emitted when the "close" button is clicked. */
@Output()
closeSearch = new EventEmitter();
filteredResults$: Observable<UserProcessModel[]>;
selectedUser: UserProcessModel = {};
filteredResults$: Observable<LightUserRepresentation[]>;
selectedUser: LightUserRepresentation = {} as any;
performSearch: PerformSearchCallback;
ngOnInit() {
this.filteredResults$ = this.results
.pipe(
map((users) => users.filter((user) => user.id !== this.selectedUser.id))
);
this.filteredResults$ = this.results.pipe(map((users) => users.filter((user) => user.id !== this.selectedUser.id)));
this.performSearch = this.performSearchCallback.bind(this);
}
onRowClick(user: UserProcessModel) {
onRowClick(user: LightUserRepresentation) {
this.selectedUser = user;
}
@@ -81,7 +88,7 @@ export class PeopleSearchComponent implements OnInit {
this.success.emit(this.selectedUser);
}
private performSearchCallback(event: any): Observable<UserProcessModel[]> {
private performSearchCallback(event: any): Observable<LightUserRepresentation[]> {
this.searchPeople.emit(event);
return this.filteredResults$;
}

View File

@@ -23,12 +23,17 @@ import { getDisplayUser } from '../../helpers/get-display-user';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
const DEFAULT_ASSIGNEE_PLACEHOLDER = 'ADF_TASK_LIST.PEOPLE.ASSIGNEE';
@Component({
selector: 'adf-people-selector',
standalone: true,
imports: [CommonModule, PeopleSearchFieldComponent, MatButtonModule, MatIconModule],
templateUrl: './people-selector.component.html',
styleUrls: ['./people-selector.component.scss'],
host: { class: 'adf-people-selector' },
@@ -36,7 +41,7 @@ const DEFAULT_ASSIGNEE_PLACEHOLDER = 'ADF_TASK_LIST.PEOPLE.ASSIGNEE';
})
export class PeopleSelectorComponent {
@Input()
peopleId: UserProcessModel;
peopleId: LightUserRepresentation;
// Poorly documented Angular magic for [(peopleId)]
@Output()
@@ -46,7 +51,7 @@ export class PeopleSelectorComponent {
searchFieldComponent: PeopleSearchFieldComponent;
performSearch: PerformSearchCallback;
selectedUser: UserProcessModel;
selectedUser: LightUserRepresentation;
defaultPlaceholder: string;
constructor(private peopleProcessService: PeopleProcessService, private translationService: TranslationService) {
@@ -55,11 +60,11 @@ export class PeopleSelectorComponent {
this.defaultPlaceholder = this.translationService.instant(DEFAULT_ASSIGNEE_PLACEHOLDER);
}
searchUser(searchWord: string): Observable<any | UserProcessModel[]> {
searchUser(searchWord: string): Observable<any | LightUserRepresentation[]> {
return this.peopleProcessService.getWorkflowUsers(undefined, searchWord).pipe(catchError(() => of([])));
}
userSelected(user: UserProcessModel): void {
userSelected(user: LightUserRepresentation): void {
this.updateUserSelection(user);
}
@@ -67,7 +72,7 @@ export class PeopleSelectorComponent {
this.updateUserSelection(undefined);
}
private updateUserSelection(user: UserProcessModel): void {
private updateUserSelection(user: LightUserRepresentation): void {
this.selectedUser = user;
this.peopleIdChange.emit(user?.id);
this.searchFieldComponent.reset();

View File

@@ -14,13 +14,13 @@
<div class="adf-assignment-container" *ngIf="showAssignment">
<adf-people-search
#peopleSearch
[headerTitle]="'ADF_TASK_LIST.DETAILS.LABELS.ADD_PEOPLE'"
[actionLabel]="'ADF_TASK_LIST.PEOPLE.ADD_USER'"
(searchPeople)="searchUser($event)"
(success)="involveUser($event)"
(closeSearch)="onCloseSearch()"
[results]="peopleSearch$"
>
<ng-container adf-people-search-title>{{ 'ADF_TASK_LIST.DETAILS.LABELS.ADD_PEOPLE' | translate }}</ng-container>
<ng-container adf-people-search-action-label>{{ 'ADF_TASK_LIST.PEOPLE.ADD_USER' | translate }}</ng-container>
</adf-people-search>
</div>
<div class="adf-assignment-list-container" id="assignment-people-list" *ngIf="hasPeople()">
@@ -36,7 +36,7 @@
[alt]="getDisplayUser(entry.row.obj.firstName, entry.row.obj.lastName, ' ')"
*ngIf="entry.row.obj.pictureId"
class="adf-people-img"
[src]="peopleProcessService.getUserImage(entry.row.obj)"
[src]="peopleProcessService.getUserImage(entry.row.obj.id.toString())"
/>
</div>
</ng-template>

View File

@@ -18,47 +18,47 @@
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { PeopleComponent } from './people.component';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
declare let jasmine: any;
const fakeUser = new UserProcessModel({
id: 'fake-id',
const fakeUser: LightUserRepresentation = {
id: 0,
firstName: 'fake-name',
lastName: 'fake-last',
email: 'fake@mail.com'
});
};
const fakeSecondUser = new UserProcessModel({
id: 'fake-involve-id',
const fakeSecondUser: LightUserRepresentation = {
id: 1,
firstName: 'fake-involve-name',
lastName: 'fake-involve-last',
email: 'fake-involve@mail.com'
});
};
describe('PeopleComponent', () => {
let activitiPeopleComponent: PeopleComponent;
let peopleComponent: PeopleComponent;
let fixture: ComponentFixture<PeopleComponent>;
let element: HTMLElement;
const userArray = [fakeUser, fakeSecondUser];
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessTestingModule]
imports: [ProcessTestingModule, PeopleComponent]
});
fixture = TestBed.createComponent(PeopleComponent);
activitiPeopleComponent = fixture.componentInstance;
peopleComponent = fixture.componentInstance;
element = fixture.nativeElement;
activitiPeopleComponent.people = [];
activitiPeopleComponent.readOnly = true;
peopleComponent.people = [];
peopleComponent.readOnly = true;
fixture.detectChanges();
});
afterEach(() => fixture.destroy());
it('should show people component title', async () => {
activitiPeopleComponent.people = [...userArray];
peopleComponent.people = [...userArray];
fixture.detectChanges();
await fixture.whenStable();
@@ -77,8 +77,8 @@ describe('PeopleComponent', () => {
describe('when there are involved people', () => {
beforeEach(() => {
activitiPeopleComponent.taskId = 'fake-task-id';
activitiPeopleComponent.people.push(...userArray);
peopleComponent.taskId = 'fake-task-id';
peopleComponent.people.push(...userArray);
fixture.detectChanges();
});
@@ -100,7 +100,7 @@ describe('PeopleComponent', () => {
});
it('should remove people involved', fakeAsync(() => {
activitiPeopleComponent.removeInvolvedUser(fakeUser);
peopleComponent.removeInvolvedUser(fakeUser);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200
});
@@ -113,7 +113,7 @@ describe('PeopleComponent', () => {
}));
it('should involve people', fakeAsync(() => {
activitiPeopleComponent.involveUser(fakeUser);
peopleComponent.involveUser(fakeUser);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200
});
@@ -126,7 +126,7 @@ describe('PeopleComponent', () => {
}));
it('should return an observable with user search results', (done) => {
activitiPeopleComponent.peopleSearch$.subscribe((users) => {
peopleComponent.peopleSearch$.subscribe((users) => {
expect(users.length).toBe(2);
expect(users[0].firstName).toBe('fake-test-1');
expect(users[0].lastName).toBe('fake-last-1');
@@ -134,7 +134,7 @@ describe('PeopleComponent', () => {
expect(users[0].id).toBe(1);
done();
});
activitiPeopleComponent.searchUser('fake-search-word');
peopleComponent.searchUser('fake-search-word');
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
@@ -158,11 +158,11 @@ describe('PeopleComponent', () => {
});
it('should return an empty list for not valid search', (done) => {
activitiPeopleComponent.peopleSearch$.subscribe((users) => {
peopleComponent.peopleSearch$.subscribe((users) => {
expect(users.length).toBe(0);
done();
});
activitiPeopleComponent.searchUser('fake-search-word');
peopleComponent.searchUser('fake-search-word');
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
@@ -174,7 +174,7 @@ describe('PeopleComponent', () => {
describe('when there are errors on service call', () => {
beforeEach(() => {
jasmine.Ajax.install();
activitiPeopleComponent.people.push(...userArray);
peopleComponent.people.push(...userArray);
fixture.detectChanges();
});
@@ -183,7 +183,7 @@ describe('PeopleComponent', () => {
});
it('should not remove user if remove involved user fail', async () => {
activitiPeopleComponent.removeInvolvedUser(fakeUser);
peopleComponent.removeInvolvedUser(fakeUser);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403
});
@@ -194,7 +194,7 @@ describe('PeopleComponent', () => {
});
it('should not involve user if involve user fail', async () => {
activitiPeopleComponent.involveUser(fakeUser);
peopleComponent.involveUser(fakeUser);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403
});

View File

@@ -20,11 +20,19 @@ import { Observable, Observer } from 'rxjs';
import { UserEventModel } from '../../../task-list/models/user-event.model';
import { PeopleSearchComponent } from '../people-search/people-search.component';
import { share } from 'rxjs/operators';
import { UserProcessModel } from '../../../common/models/user-process.model';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { LightUserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
import { DataTableModule } from '@alfresco/adf-core';
import { PeopleListComponent } from '../people-list/people-list.component';
@Component({
selector: 'adf-people',
standalone: true,
imports: [CommonModule, MatCardModule, TranslateModule, MatIconModule, DataTableModule, PeopleSearchComponent, PeopleListComponent],
templateUrl: './people.component.html',
styleUrls: ['./people.component.scss'],
encapsulation: ViewEncapsulation.None
@@ -32,7 +40,7 @@ import { PeopleProcessService } from '../../../common/services/people-process.se
export class PeopleComponent {
/** The array of User objects to display. */
@Input()
people: UserProcessModel[] = [];
people: LightUserRepresentation[] = [];
/** The numeric ID of the task. */
@Input()
@@ -49,24 +57,12 @@ export class PeopleComponent {
error = new EventEmitter<any>();
showAssignment: boolean = false;
peopleSearch$: Observable<UserProcessModel[]>;
peopleSearch$: Observable<LightUserRepresentation[]>;
private peopleSearchObserver: Observer<UserProcessModel[]>;
private peopleSearchObserver: Observer<LightUserRepresentation[]>;
constructor(public peopleProcessService: PeopleProcessService) {
this.peopleSearch$ = new Observable<UserProcessModel[]>((observer) => (this.peopleSearchObserver = observer)).pipe(share());
}
involveUserAndCloseSearch() {
if (this.peopleSearch) {
this.peopleSearch.involveUserAndClose();
}
}
involveUserWithoutCloseSearch() {
if (this.peopleSearch) {
this.peopleSearch.involveUser();
}
this.peopleSearch$ = new Observable<LightUserRepresentation[]>((observer) => (this.peopleSearchObserver = observer)).pipe(share());
}
searchUser(searchedWord: string) {
@@ -78,8 +74,8 @@ export class PeopleComponent {
);
}
involveUser(user: UserProcessModel) {
if (user?.id) {
involveUser(user: LightUserRepresentation) {
if (user?.id !== undefined) {
this.peopleProcessService.involveUserWithTask(this.taskId, user.id.toString()).subscribe(
() => (this.people = [...this.people, user]),
() => this.error.emit('Impossible to involve user with task')
@@ -87,7 +83,7 @@ export class PeopleComponent {
}
}
removeInvolvedUser(user: UserProcessModel) {
removeInvolvedUser(user: LightUserRepresentation) {
this.peopleProcessService.removeInvolvedUser(this.taskId, user.id.toString()).subscribe(
() => {
this.people = this.people.filter((involvedUser) => involvedUser.id !== user.id);

View File

@@ -1,25 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Directive } from '@angular/core';
/**
* Directive selectors without adf- prefix will be deprecated on 3.0.0
*/
// eslint-disable-next-line @angular-eslint/directive-selector
@Directive({ selector: 'adf-people-search-action-label, people-search-action-label' })
export class PeopleSearchActionLabelDirective { }

View File

@@ -1,25 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Directive } from '@angular/core';
/**
* Directive selectors without adf- prefix will be deprecated on 3.0.0
*/
// eslint-disable-next-line @angular-eslint/directive-selector
@Directive({ selector: '[adf-people-search-title]' })
export class PeopleSearchTitleDirective { }

View File

@@ -16,6 +16,6 @@
*/
import { Observable } from 'rxjs';
import { UserProcessModel } from '../../common/models/user-process.model';
import { LightUserRepresentation } from '@alfresco/js-api';
export type PerformSearchCallback = (searchWord: string) => Observable<UserProcessModel[]>;
export type PerformSearchCallback = (searchWord: string) => Observable<LightUserRepresentation[]>;

View File

@@ -15,47 +15,15 @@
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule } from '@alfresco/adf-core';
import { PeopleComponent } from './components/people/people.component';
import { PeopleListComponent } from './components/people-list/people-list.component';
import { PeopleSearchComponent } from './components/people-search/people-search.component';
import { PeopleSearchFieldComponent } from './components/people-search-field/people-search-field.component';
import { PeopleSelectorComponent } from './components/people-selector/people-selector.component';
import { PeopleSearchActionLabelDirective } from './directives/people-search-action-label.directive';
import { PeopleSearchTitleDirective } from './directives/people-search-title.directive';
@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
MaterialModule,
CommonModule,
CoreModule
],
declarations: [
PeopleComponent,
PeopleSearchComponent,
PeopleSearchFieldComponent,
PeopleSelectorComponent,
PeopleSearchTitleDirective,
PeopleSearchActionLabelDirective,
PeopleListComponent
],
exports: [
PeopleComponent,
PeopleSearchComponent,
PeopleSearchFieldComponent,
PeopleSelectorComponent,
PeopleSearchTitleDirective,
PeopleSearchActionLabelDirective,
PeopleListComponent
]
imports: [PeopleComponent, PeopleSearchComponent, PeopleSearchFieldComponent, PeopleSelectorComponent, PeopleListComponent],
exports: [PeopleComponent, PeopleSearchComponent, PeopleSearchFieldComponent, PeopleSelectorComponent, PeopleListComponent]
})
export class PeopleModule {
}
export class PeopleModule {}

View File

@@ -23,7 +23,4 @@ export * from './components/people-selector/people-selector.component';
export * from './interfaces/perform-search-callback.interface';
export * from './directives/people-search-action-label.directive';
export * from './directives/people-search-title.directive';
export * from './people.module';

View File

@@ -15,4 +15,6 @@
* limitations under the License.
*/
export * from './public-api';
export * from './process-comments.component';
export * from './services/comment-process.service';
export * from './process-comments.module';

View File

@@ -18,34 +18,38 @@
import { Injectable } from '@angular/core';
import { Observable, from, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { CommentModel, UserProcessModel, CommentsService } from '@alfresco/adf-core';
import { fakeUser1 } from '../mock/comment-process.mock';
import { CommentModel, CommentsService } from '@alfresco/adf-core';
export const fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName', avatarId: '0' };
@Injectable()
export class CommentProcessServiceMock implements Partial<CommentsService> {
private comments: CommentModel [] = [];
private comments: CommentModel[] = [];
get(_id: string): Observable<CommentModel[]> {
const user = new UserProcessModel(fakeUser1);
this.comments.push(new CommentModel({
id: 46,
message: 'Hello from Process Model',
created: new Date('2022-08-02T03:37:30.010+0000'),
createdBy: user
}));
this.comments.push(
new CommentModel({
id: 46,
message: 'Hello from Process Model',
created: new Date('2022-08-02T03:37:30.010+0000'),
createdBy: fakeUser1
})
);
return of(this.comments);
}
add(_id: string, _message: string): Observable<CommentModel> {
return from(this.comments).pipe(
map((response) => new CommentModel({
id: response.id,
message: response.message,
created: response.created,
createdBy: response.createdBy
}))
map(
(response) =>
new CommentModel({
id: response.id,
message: response.message,
created: response.created,
createdBy: response.createdBy
})
)
);
}
}

View File

@@ -1,40 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { EcmUserModel } from '@alfresco/adf-core';
export const fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' };
export const testUser: EcmUserModel = {
id: '44',
email: 'test.user@hyland.com',
firstName: 'Test',
lastName: 'User',
company: {
organization: '',
address1: '',
address2: '',
address3: '',
postcode: '',
telephone: '',
fax: '',
email: ''
},
enabled: true,
isAdmin: undefined,
avatarId: '044'
};

View File

@@ -1,15 +1,21 @@
<div class="adf-comments-container">
<div id="comment-header" class="adf-comments-header">
{{'ADF_PROCESS_LIST.DETAILS.COMMENTS.HEADER' | translate: { count: comments?.length} }}
{{ 'ADF_PROCESS_LIST.DETAILS.COMMENTS.HEADER' | translate : { count: comments?.length } }}
</div>
<div class="adf-comments-input-container" *ngIf="!isReadOnly()">
<mat-form-field class="adf-full-width">
<input matInput id="comment-input" placeholder="{{'ADF_PROCESS_LIST.DETAILS.COMMENTS.ADD' | translate}}" [(ngModel)]="message" (keyup.enter)="add()" (keyup.esc)="clear()">
<input
matInput
id="comment-input"
placeholder="{{ 'ADF_PROCESS_LIST.DETAILS.COMMENTS.ADD' | translate }}"
[(ngModel)]="message"
(keyup.enter)="add()"
(keyup.escape)="clear()"
/>
</mat-form-field>
</div>
<div *ngIf="comments.length > 0">
<adf-comment-list [comments]="comments">
</adf-comment-list>
<adf-comment-list [comments]="comments"> </adf-comment-list>
</div>
</div>

View File

@@ -31,7 +31,7 @@ describe('ProcessCommentsComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ProcessTestingModule]
imports: [ProcessTestingModule, ProcessCommentsComponent]
});
fixture = TestBed.createComponent(ProcessCommentsComponent);
component = fixture.componentInstance;

View File

@@ -15,14 +15,27 @@
* limitations under the License.
*/
import { CommentModel } from '@alfresco/adf-core';
import { ADF_COMMENTS_SERVICE, CommentListModule, CommentModel } from '@alfresco/adf-core';
import { CommentProcessService } from './services/comment-process.service';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Observable, Observer, Subject } from 'rxjs';
import { share, takeUntil } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'adf-process-instance-comments',
standalone: true,
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatInputModule, CommentListModule, FormsModule],
providers: [
{
provide: ADF_COMMENTS_SERVICE,
useClass: CommentProcessService
}
],
templateUrl: './process-comments.component.html',
styleUrls: ['./process-comments.component.scss'],
encapsulation: ViewEncapsulation.None,

View File

@@ -15,35 +15,11 @@
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ADF_COMMENTS_SERVICE, CoreModule } from '@alfresco/adf-core';
import { ProcessCommentsComponent } from './process-comments.component';
import { CommentProcessService } from './services/comment-process.service';
@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
MaterialModule,
CommonModule,
CoreModule
],
declarations: [
ProcessCommentsComponent
],
exports: [
ProcessCommentsComponent
],
providers: [
{
provide: ADF_COMMENTS_SERVICE,
useClass: CommentProcessService
}
]
imports: [ProcessCommentsComponent],
exports: [ProcessCommentsComponent]
})
export class ProcessCommentsModule {
}
export class ProcessCommentsModule {}

View File

@@ -1,22 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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-comments.component';
export * from './services/comment-process.service';
export * from './process-comments.module';

View File

@@ -21,7 +21,6 @@ import { CommentModel, AlfrescoApiService, CommentsService, User } from '@alfres
import { map } from 'rxjs/operators';
import { ActivitiCommentsApi } from '@alfresco/js-api';
import { PeopleProcessService } from '../../common/services/people-process.service';
import { UserProcessModel } from '../../common/models/user-process.model';
@Injectable({
providedIn: 'root'
@@ -44,19 +43,14 @@ export class CommentProcessService implements CommentsService {
get(id: string): Observable<CommentModel[]> {
return from(this.commentsApi.getProcessInstanceComments(id)).pipe(
map((response) => {
const comments: CommentModel[] = [];
response.data.forEach((comment) => {
const user = new UserProcessModel(comment.createdBy);
comments.push(
new CommentModel({
id: comment.id,
message: comment.message,
created: comment.created,
createdBy: new User(user)
})
);
return response.data.map((comment) => {
return new CommentModel({
id: comment.id,
message: comment.message,
created: comment.created,
createdBy: new User(comment.createdBy)
});
});
return comments;
})
);
}
@@ -82,7 +76,7 @@ export class CommentProcessService implements CommentsService {
);
}
getUserImage(user: UserProcessModel): string {
return this.peopleProcessService.getUserImage(user);
getUserImage(userId: string): string {
return this.peopleProcessService.getUserImage(userId);
}
}

View File

@@ -21,13 +21,14 @@ import { of, throwError } from 'rxjs';
import { ProcessService } from './../services/process.service';
import { DownloadService } from '@alfresco/adf-core';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { ProcessInstanceAuditInfoRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-basic-button',
template: ` <button
id="auditButton"
adf-process-audit
[process-id]="1234"
[process-id]="'1234'"
[download]="download"
[fileName]="fileName"
[format]="format"
@@ -42,8 +43,12 @@ class BasicButtonComponent {
fileName: string;
format: string;
onAuditClick() {}
onAuditError() {}
onAuditClick(_event: any) {
/* do nothing */
}
onAuditError(_event: any) {
/* do nothing */
}
}
describe('ProcessAuditDirective', () => {
@@ -140,11 +145,11 @@ describe('ProcessAuditDirective', () => {
component.fileName = 'FakeAuditName';
component.format = 'json';
component.download = true;
const auditJson = {
processInstanceId: 42516,
const auditJson: ProcessInstanceAuditInfoRepresentation = {
processInstanceId: '42516',
processInstanceName: 'Fake Process - August 3rd 2017',
processDefinitionName: 'Claim Approval Process',
processDefinitionVersion: 1,
processDefinitionVersion: '1',
processInstanceStartTime: 'Thu Aug 03 15:32:47 UTC 2017',
processInstanceEndTime: null,
// eslint-disable-next-line @cspell/spellchecker

View File

@@ -17,18 +17,37 @@
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
import { from, of, throwError } from 'rxjs';
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { ProcessFilterService } from '../services/process-filter.service';
import { ProcessFiltersComponent } from './process-filters.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { fakeProcessFilters } from '../../mock/process/process-filters.mock';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { NavigationStart, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ProcessInstanceFilterRepresentation, UserProcessInstanceFilterRepresentation } from '@alfresco/js-api';
const fakeProcessFilters: UserProcessInstanceFilterRepresentation[] = [
{
id: 10,
name: 'FakeCompleted',
icon: 'glyphicon-th',
filter: { state: 'open', assignment: 'fake-involved' }
},
{
id: 20,
name: 'FakeAll',
icon: 'glyphicon-random',
filter: { state: 'open', assignment: 'fake-assignee' }
},
{
id: 30,
name: 'Running',
icon: 'glyphicon-ok-sign',
filter: { state: 'open', assignment: 'fake-running' }
}
];
describe('ProcessFiltersComponent', () => {
let filterList: ProcessFiltersComponent;
let fixture: ComponentFixture<ProcessFiltersComponent>;
@@ -95,7 +114,7 @@ describe('ProcessFiltersComponent', () => {
it('should emit the selected filter based on the filterParam input', async () => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(fakeProcessFilters));
filterList.filterParam = new FilterProcessRepresentationModel({ id: 10 });
filterList.filterParam = { id: 10 } as any;
const appId = '1';
const change = new SimpleChange(null, appId, true);
@@ -130,7 +149,7 @@ describe('ProcessFiltersComponent', () => {
const change = new SimpleChange(null, appId, true);
filterList.currentFilter = nonExistingFilterParam;
filterList.filterParam = new FilterProcessRepresentationModel(nonExistingFilterParam);
filterList.filterParam = nonExistingFilterParam as any;
filterList.ngOnChanges({ appId: change });
fixture.detectChanges();
@@ -180,11 +199,11 @@ describe('ProcessFiltersComponent', () => {
});
it('should emit an event when a filter is selected', async () => {
const currentFilter = new FilterProcessRepresentationModel({
const currentFilter: UserProcessInstanceFilterRepresentation = {
id: 10,
name: 'FakeCompleted',
filter: { state: 'open', assignment: 'fake-involved' }
});
};
let lastValue: UserProcessInstanceFilterRepresentation;
filterList.filterClicked.subscribe((filter) => (lastValue = filter));
@@ -226,10 +245,10 @@ describe('ProcessFiltersComponent', () => {
});
it('should return the current filter after one is selected', () => {
const filter = new FilterProcessRepresentationModel({
const filter: UserProcessInstanceFilterRepresentation = {
name: 'FakeAll',
filter: { state: 'open', assignment: 'fake-assignee' }
});
};
expect(filterList.currentFilter).toBeUndefined();
filterList.selectFilter(filter);
expect(filterList.getCurrentFilter()).toBe(filter);
@@ -238,7 +257,7 @@ describe('ProcessFiltersComponent', () => {
it('should select the filter passed as input by id', async () => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(fakeProcessFilters));
filterList.filterParam = new FilterProcessRepresentationModel({ id: 20 });
filterList.filterParam = { id: 20 } as any;
const appId = 1;
const change = new SimpleChange(null, appId, true);
@@ -255,7 +274,7 @@ describe('ProcessFiltersComponent', () => {
it('should select the filter passed as input by name', async () => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(of(fakeProcessFilters));
filterList.filterParam = new FilterProcessRepresentationModel({ name: 'FakeAll' });
filterList.filterParam = { name: 'FakeAll' } as any;
const appId = 1;
const change = new SimpleChange(null, appId, true);

View File

@@ -17,8 +17,7 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ProcessInstanceFilterRepresentation, UserProcessInstanceFilterRepresentation } from '@alfresco/js-api';
import { Observable, Subject } from 'rxjs';
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { Subject } from 'rxjs';
import { ProcessFilterService } from './../services/process-filter.service';
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { IconModel } from '../../app-list/icon.model';
@@ -38,7 +37,7 @@ export class ProcessFiltersComponent implements OnInit, OnChanges, OnDestroy {
* (ie, the first filter in the list) is selected.
*/
@Input()
filterParam: FilterProcessRepresentationModel;
filterParam: UserProcessInstanceFilterRepresentation;
/** Emitted when a filter is being clicked from the UI. */
@Output()
@@ -62,14 +61,12 @@ export class ProcessFiltersComponent implements OnInit, OnChanges, OnDestroy {
/** Toggle to show or hide the filter's icon. */
@Input()
showIcon: boolean = true;
showIcon = true;
/** Emitted when a filter is being selected based on the filterParam input. */
@Output()
filterSelected = new EventEmitter<UserProcessInstanceFilterRepresentation>();
filter$: Observable<ProcessInstanceFilterRepresentation>;
currentFilter: ProcessInstanceFilterRepresentation;
filters: UserProcessInstanceFilterRepresentation[] = [];
@@ -186,7 +183,7 @@ export class ProcessFiltersComponent implements OnInit, OnChanges, OnDestroy {
*
* @param filterParam filter parameter
*/
selectProcessFilter(filterParam: FilterProcessRepresentationModel): void {
selectProcessFilter(filterParam: UserProcessInstanceFilterRepresentation): void {
if (filterParam) {
const newFilter = this.filters.find(
(processFilter, index) =>
@@ -211,16 +208,6 @@ export class ProcessFiltersComponent implements OnInit, OnChanges, OnDestroy {
this.selectProcessFilter(this.processFilterService.getRunningFilterInstance(null));
}
/**
* Select as default task filter the first in the list
*/
selectDefaultTaskFilter() {
if (!this.isFilterListEmpty()) {
this.currentFilter = this.filters[0];
this.filterSelected.emit(this.filters[0]);
}
}
/**
* Get the current task
*

View File

@@ -18,11 +18,10 @@
import { DatePipe } from '@angular/common';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { TaskDetailsEvent } from '../../task-list';
import { ProcessInstance } from '../models/process-instance.model';
import { ProcessService } from './../services/process.service';
import { ProcessInstanceHeaderComponent } from './process-instance-header.component';
import { ProcessInstanceTasksComponent } from './process-instance-tasks.component';
import { ProcessInstanceRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-process-instance-details',
@@ -64,9 +63,9 @@ export class ProcessInstanceDetailsComponent implements OnChanges {
@Output()
showProcessDiagram = new EventEmitter<any>();
processInstanceDetails: ProcessInstance;
processInstanceDetails: ProcessInstanceRepresentation;
constructor(private activitiProcess: ProcessService) {}
constructor(private processService: ProcessService) {}
ngOnChanges(changes: SimpleChanges) {
const processInstanceId = changes['processInstanceId'];
@@ -89,7 +88,7 @@ export class ProcessInstanceDetailsComponent implements OnChanges {
load(processId: string) {
if (processId) {
this.activitiProcess.getProcess(processId).subscribe((res) => {
this.processService.getProcess(processId).subscribe((res) => {
this.processInstanceDetails = res;
});
}
@@ -100,7 +99,7 @@ export class ProcessInstanceDetailsComponent implements OnChanges {
}
cancelProcess() {
this.activitiProcess.cancelProcess(this.processInstanceId).subscribe(
this.processService.cancelProcess(this.processInstanceId).subscribe(
(data) => {
this.processCancelled.emit(data);
},

View File

@@ -17,7 +17,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppConfigService } from '@alfresco/adf-core';
import { ProcessInstance } from '../models/process-instance.model';
import { exampleProcess } from '../../mock';
import { ProcessInstanceHeaderComponent } from './process-instance-header.component';
import { ProcessTestingModule } from '../../testing/process.testing.module';
@@ -34,7 +33,7 @@ describe('ProcessInstanceHeaderComponent', () => {
fixture = TestBed.createComponent(ProcessInstanceHeaderComponent);
component = fixture.componentInstance;
component.processInstance = new ProcessInstance(exampleProcess);
component.processInstance = exampleProcess;
appConfigService = TestBed.inject(AppConfigService);
appConfigService.config['adf-process-instance-header'] = {};

View File

@@ -24,7 +24,7 @@ import {
TranslationService
} from '@alfresco/adf-core';
import { Component, Input, OnChanges } from '@angular/core';
import { ProcessInstance } from '../models/process-instance.model';
import { ProcessInstanceRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-process-instance-header',
@@ -34,7 +34,7 @@ import { ProcessInstance } from '../models/process-instance.model';
export class ProcessInstanceHeaderComponent implements OnChanges {
/** (**required**) Full details of the process instance to display information about. */
@Input()
processInstance: ProcessInstance;
processInstance: ProcessInstanceRepresentation;
properties: CardViewItem[];
dateFormat: string;

View File

@@ -21,13 +21,13 @@ import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { TaskDetailsModel } from '../../task-list';
import { taskDetailsMock } from '../../mock';
import { ProcessInstance } from './../models/process-instance.model';
import { ProcessService } from './../services/process.service';
import { ProcessInstanceTasksComponent } from './process-instance-tasks.component';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatListItemHarness } from '@angular/material/list/testing';
import { ProcessInstanceRepresentation } from '@alfresco/js-api';
describe('ProcessInstanceTasksComponent', () => {
let component: ProcessInstanceTasksComponent;
@@ -35,7 +35,7 @@ describe('ProcessInstanceTasksComponent', () => {
let loader: HarnessLoader;
let processService: ProcessService;
const exampleProcessInstance = new ProcessInstance({ id: '123' });
const exampleProcessInstance: ProcessInstanceRepresentation = { id: '123' };
beforeEach(() => {
TestBed.configureTestingModule({

View File

@@ -20,9 +20,9 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChange
import { MatDialog } from '@angular/material/dialog';
import { Observable, Observer, Subject } from 'rxjs';
import { TaskDetailsEvent, TaskDetailsModel } from '../../task-list';
import { ProcessInstance } from '../models/process-instance.model';
import { ProcessService } from './../services/process.service';
import { share, takeUntil } from 'rxjs/operators';
import { ProcessInstanceRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-process-instance-tasks',
@@ -32,7 +32,7 @@ import { share, takeUntil } from 'rxjs/operators';
export class ProcessInstanceTasksComponent implements OnInit, OnChanges, OnDestroy {
/** The ID of the process instance to display tasks for. */
@Input()
processInstanceDetails: ProcessInstance;
processInstanceDetails: ProcessInstanceRepresentation;
/**
* Toggles whether to show a refresh button next to the list of tasks to allow

View File

@@ -1,4 +1,4 @@
<adf-datatable #dataTable
<adf-datatable
[data]="data"
[rows]="rows"
[columns]="columns"

View File

@@ -31,15 +31,36 @@ import {
DEFAULT_PAGINATION
} from '@alfresco/adf-core';
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { processPresetsDefaultModel } from '../models/process-preset.model';
import { ProcessService } from '../services/process.service';
import { BehaviorSubject } from 'rxjs';
import { ProcessListModel } from '../models/process-list.model';
import { finalize } from 'rxjs/operators';
import {
ProcessInstanceQueryRepresentation,
ProcessInstanceQueryRepresentationSort,
ProcessInstanceQueryRepresentationState,
ResultListDataRepresentationProcessInstanceRepresentation
} from '@alfresco/js-api';
const PRESET_KEY = 'adf-process-list.presets';
export const processPresetsDefaultModel = {
default: [
{
key: 'name',
type: 'text',
title: 'ADF_PROCESS_LIST.PROPERTIES.NAME',
sortable: true
},
{
key: 'created',
type: 'text',
title: 'ADF_PROCESS_LIST.PROPERTIES.CREATED',
cssClass: 'hidden',
sortable: true
}
]
};
@Component({
selector: 'adf-process-instance-list',
styleUrls: ['./process-list.component.css'],
@@ -62,18 +83,17 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
/** The id of the process instance. */
@Input()
processInstanceId: number | string;
processInstanceId: string;
/** Defines the state of the processes. Possible values are `running`, `completed` and `all` */
/** Defines the state of the processes. */
@Input()
state: string;
state: ProcessInstanceQueryRepresentationState;
/**
* Defines the sort ordering of the list. Possible values are `created-desc`, `created-asc`,
* `ended-desc`, `ended-asc`.
* Defines the sort ordering of the list.
*/
@Input()
sort: string;
sort: ProcessInstanceQueryRepresentationSort;
/** The page number of the processes to fetch. */
@Input()
@@ -137,14 +157,14 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
/** Emitted when the list of process instances has been loaded successfully from the server. */
// eslint-disable-next-line @angular-eslint/no-output-native
@Output()
success = new EventEmitter<ProcessListModel>();
success = new EventEmitter<ResultListDataRepresentationProcessInstanceRepresentation>();
/** Emitted when an error occurs while loading the list of process instances from the server. */
// eslint-disable-next-line @angular-eslint/no-output-native
@Output()
error = new EventEmitter<any>();
requestNode: ProcessFilterParamRepresentationModel;
requestNode: ProcessInstanceQueryRepresentation;
currentInstanceId: string;
isLoading: boolean = true;
rows: any[] = [];
@@ -271,8 +291,8 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
return skipCount && maxItems ? Math.floor(skipCount / maxItems) : 0;
}
private createRequestNode(): ProcessFilterParamRepresentationModel {
return new ProcessFilterParamRepresentationModel({
private createRequestNode(): ProcessInstanceQueryRepresentation {
return {
appDefinitionId: this.appId,
processDefinitionId: this.processDefinitionId,
processInstanceId: this.processInstanceId,
@@ -281,7 +301,7 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
page: this.page,
size: this.size,
start: 0
});
};
}
private isSortChanged(changes: SimpleChanges): boolean {
@@ -318,7 +338,7 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
return changed;
}
private load(requestNode: ProcessFilterParamRepresentationModel) {
private load(requestNode: ProcessInstanceQueryRepresentation) {
this.isLoading = true;
this.processService
.getProcesses(requestNode)
@@ -329,7 +349,7 @@ export class ProcessInstanceListComponent extends DataTableSchema implements OnC
this.selectFirst();
this.success.emit(response);
this.pagination.next({
count: response.data.length,
count: (response.data || []).length,
maxItems: this.size,
skipCount: this.page * this.size,
totalItems: response.total

View File

@@ -50,7 +50,7 @@
*ngIf="showSelectProcessDropdown"
mat-icon-button
(click)="displayDropdown($event)"
[disabled]="disableDropdownButton()"
[disabled]="isDropdownDisabled()"
[attr.aria-label]="'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.TYPE' | translate | uppercase">
<mat-icon>arrow_drop_down</mat-icon>
</button>

View File

@@ -21,7 +21,6 @@ import { AppConfigService } from '@alfresco/adf-core';
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { of, throwError } from 'rxjs';
import { MatSelectChange } from '@angular/material/select';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessService } from '../services/process.service';
import { newProcess, taskFormMock, testProcessDef, testMultipleProcessDefs, testProcessDefWithForm, testProcessDefinitions } from '../../mock';
import { StartProcessInstanceComponent } from './start-process.component';
@@ -32,6 +31,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
import { MatFormFieldHarness } from '@angular/material/form-field/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatAutocompleteHarness } from '@angular/material/autocomplete/testing';
import { RestVariable } from '@alfresco/js-api';
describe('StartProcessComponent', () => {
let appConfig: AppConfigService;
@@ -358,7 +358,7 @@ describe('StartProcessComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(component.selectedProcessDef.name).not.toBeDefined();
expect(component.selectedProcessDef).toBeUndefined();
});
describe('dropdown', () => {
@@ -468,9 +468,9 @@ describe('StartProcessComponent', () => {
});
it('should call service to start process with the variables setted', async () => {
const inputProcessVariable: ProcessInstanceVariable[] = [];
const inputProcessVariable: RestVariable[] = [];
const variable: ProcessInstanceVariable = {};
const variable: RestVariable = {};
variable.name = 'nodeId';
variable.value = 'id';

View File

@@ -18,9 +18,6 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild, ViewEncapsulation, OnDestroy } from '@angular/core';
import { AppConfigService, AppConfigValues, FormValues, LocalizedDatePipe } from '@alfresco/adf-core';
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessDefinitionRepresentation } from './../models/process-definition.model';
import { ProcessInstance } from './../models/process-instance.model';
import { ProcessService } from './../services/process.service';
import { UntypedFormControl, Validators, AbstractControl } from '@angular/forms';
import { Observable, Subject, forkJoin } from 'rxjs';
@@ -28,8 +25,14 @@ import { map, takeUntil } from 'rxjs/operators';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatSelectChange } from '@angular/material/select';
import { StartFormComponent } from '../../form';
import { Node, RelatedContentRepresentation } from '@alfresco/js-api';
import { AppDefinitionRepresentationModel } from '../../task-list';
import {
AppDefinitionRepresentation,
Node,
ProcessInstanceRepresentation,
RelatedContentRepresentation,
ProcessDefinitionRepresentation,
RestVariable
} from '@alfresco/js-api';
import { ActivitiContentService } from '../../form/services/activiti-alfresco.service';
import { getTime } from 'date-fns';
@@ -64,7 +67,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
* [RestVariable](https://github.com/Alfresco/alfresco-js-api/tree/master/src/alfresco-activiti-rest-api/docs/RestVariable.md).
*/
@Input()
variables: ProcessInstanceVariable[];
variables: RestVariable[];
/** Parameter to pass form field values in the start form if one is associated. */
@Input()
@@ -76,23 +79,23 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
/** Hide or show the process selection dropdown. */
@Input()
showSelectProcessDropdown: boolean = true;
showSelectProcessDropdown = true;
/** Hide or show application selection dropdown. */
@Input()
showSelectApplicationDropdown?: boolean = false;
showSelectApplicationDropdown? = false;
/** Parameter to enable selection of process when filtering. */
@Input()
processFilterSelector?: boolean = true;
processFilterSelector? = true;
/** Emitted when the process starts. */
@Output()
start = new EventEmitter<ProcessInstance>();
start = new EventEmitter<ProcessInstanceRepresentation>();
/** Emitted when the process is canceled. */
@Output()
cancel: EventEmitter<void> = new EventEmitter<void>();
cancel = new EventEmitter<void>();
/** Emitted when an error occurs. */
@Output()
@@ -104,7 +107,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
/** Emitted when application selection changes. */
@Output()
applicationSelection = new EventEmitter<AppDefinitionRepresentationModel>();
applicationSelection = new EventEmitter<AppDefinitionRepresentation>();
@ViewChild('startForm')
startForm: StartFormComponent;
@@ -119,17 +122,18 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
filteredProcessesDefinitions$: Observable<ProcessDefinitionRepresentation[]>;
maxProcessNameLength: number = MAX_LENGTH;
alfrescoRepositoryName: string;
applications: AppDefinitionRepresentationModel[] = [];
selectedApplication: AppDefinitionRepresentationModel;
applications: AppDefinitionRepresentation[] = [];
selectedApplication: AppDefinitionRepresentation;
isProcessDefinitionsLoading = true;
isAppsLoading = true;
movedNodeToPS: FormValues;
private onDestroy$ = new Subject<boolean>();
constructor(
private activitiProcess: ProcessService,
private activitiContentService: ActivitiContentService,
private processService: ProcessService,
private contentService: ActivitiContentService,
private appsProcessService: AppsProcessService,
private appConfig: AppConfigService,
private datePipe: LocalizedDatePipe
@@ -150,7 +154,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
takeUntil(this.onDestroy$)
);
this.activitiContentService.getAlfrescoRepositories().subscribe((repoList) => {
this.contentService.getAlfrescoRepositories().subscribe((repoList) => {
if (repoList?.[0]) {
const alfrescoRepository = repoList[0];
this.alfrescoRepositoryName = `alfresco-${alfrescoRepository.id}-${alfrescoRepository.name}`;
@@ -168,42 +172,37 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
this.moveNodeFromCStoPS();
}
if (this.isAppIdChanged(changes)) {
this.appId = changes['appId'].currentValue;
this.load();
const appId = changes['appId'];
if (appId?.currentValue) {
this.load(appId.currentValue);
}
if (this.isProcessDefinitionChanged(changes)) {
this.processDefinitionName = changes['processDefinitionName'].currentValue;
this.filterProcessDefinitionByName();
const processDefinitionName = changes['processDefinitionName'];
if (processDefinitionName?.currentValue) {
this.filterProcessDefinitionByName(processDefinitionName.currentValue);
}
}
getSelectedProcess(selectedProcess: string): ProcessDefinitionRepresentation {
let processSelected = this.processDefinitions.find((process) => process.name.toLowerCase() === selectedProcess);
if (!processSelected) {
processSelected = new ProcessDefinitionRepresentation();
}
return processSelected;
private getSelectedProcess(selectedProcess: string): ProcessDefinitionRepresentation {
return this.processDefinitions.find((process) => process.name.toLowerCase() === selectedProcess);
}
load() {
private load(appId?: number) {
if (this.showSelectApplicationDropdown) {
this.loadApps();
} else {
this.loadProcessDefinitions(this.appId);
this.loadProcessDefinitions(appId);
}
}
loadProcessDefinitions(appId: any): void {
loadProcessDefinitions(appId: number): void {
this.isProcessDefinitionsLoading = true;
this.resetSelectedProcessDefinition();
this.activitiProcess
this.processService
.getProcessDefinitions(appId)
.pipe(
map((processDefinitionRepresentations: ProcessDefinitionRepresentation[]) => {
map((processDefinitionRepresentations) => {
let currentProcessDef: ProcessDefinitionRepresentation;
if (processDefinitionRepresentations.length === 1) {
@@ -236,9 +235,9 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
);
}
filterProcessDefinitionByName() {
if (this.processDefinitionName) {
const filteredProcessDef = this.processDefinitions.find((processDefinition) => processDefinition.name === this.processDefinitionName);
private filterProcessDefinitionByName(definitionName: string) {
if (definitionName) {
const filteredProcessDef = this.processDefinitions.find((processDefinition) => processDefinition.name === definitionName);
if (filteredProcessDef) {
this.processDefinitionSelectionChanged(filteredProcessDef);
@@ -247,14 +246,15 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
}
}
loadApps() {
private loadApps() {
this.isAppsLoading = true;
this.appsProcessService
.getDeployedApplications()
.pipe(
map((response: AppDefinitionRepresentationModel[]) => {
const applications = this.removeDefaultApps(response);
let currentApplication: AppDefinitionRepresentationModel;
map((response) => {
const applications = response.filter((app) => app.id);
let currentApplication: AppDefinitionRepresentation;
if (applications && applications.length === 1) {
currentApplication = applications[0];
@@ -285,7 +285,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
);
}
loadProcessDefinitionsBasedOnSelectedApp() {
private loadProcessDefinitionsBasedOnSelectedApp() {
if (this.selectedApplication?.id) {
this.loadProcessDefinitions(this.selectedApplication ? this.selectedApplication.id : null);
} else {
@@ -314,11 +314,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
return !!this.selectedProcessDef?.id;
}
isProcessDefinitionsEmpty(): boolean {
return this.processDefinitions.length === 0;
}
disableDropdownButton(): boolean {
isDropdownDisabled(): boolean {
return this.showSelectApplicationDropdown && !this.isAppSelected();
}
@@ -338,7 +334,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
const currentValue = Array.isArray(this.values[key]) ? this.values[key] : [this.values[key]];
const contents = currentValue
.filter((value: any) => !!value?.isFile)
.map((content: Node) => this.activitiContentService.applyAlfrescoNode(content, null, accountIdentifier));
.map((content: Node) => this.contentService.applyAlfrescoNode(content, null, accountIdentifier));
forkJoin(contents).subscribe((res: RelatedContentRepresentation[]) => {
this.movedNodeToPS = { [key]: [...res] };
});
@@ -349,7 +345,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
startProcess(outcome?: string) {
if (this.selectedProcessDef?.id && this.nameController.value) {
const formValues = this.startForm ? this.startForm.form.values : undefined;
this.activitiProcess.startProcess(this.selectedProcessDef.id, this.nameController.value, outcome, formValues, this.variables).subscribe(
this.processService.startProcess(this.selectedProcessDef.id, this.nameController.value, outcome, formValues, this.variables).subscribe(
(res) => {
this.name = '';
this.start.emit(res);
@@ -369,7 +365,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
return this.selectedProcessDef?.hasStartForm;
}
isStartFormMissingOrValid(): boolean {
private isStartFormMissingOrValid(): boolean {
if (this.startForm) {
return this.startForm.form?.isValid;
} else {
@@ -404,7 +400,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
return undefined;
}
displayDropdown(event) {
displayDropdown(event: Event) {
event.stopPropagation();
if (!this.inputAutocomplete.panelOpen) {
this.processDefinitionInput.setValue('');
@@ -424,7 +420,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
processDefinitionSelectionChanged(processDefinition: ProcessDefinitionRepresentation) {
if (processDefinition) {
const processInstanceDetails = new ProcessInstance({ processDefinitionName: processDefinition.name });
const processInstanceDetails: ProcessInstanceRepresentation = { processDefinitionName: processDefinition.name };
const processName = this.formatProcessName(this.name, processInstanceDetails);
this.processNameInput.setValue(processName);
this.processNameInput.markAsDirty();
@@ -444,10 +440,6 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
return !!this.selectedApplication?.id;
}
private removeDefaultApps(apps: AppDefinitionRepresentationModel[]): AppDefinitionRepresentationModel[] {
return apps.filter((app) => app.id);
}
private resetSelectedProcessDefinition() {
this.selectedProcessDef = undefined;
if (this.processDefinitionInput) {
@@ -470,17 +462,6 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
}
}
private isAppIdChanged(changes: SimpleChanges) {
return changes['appId']?.currentValue && changes['appId'].currentValue !== changes['appId'].previousValue;
}
private isProcessDefinitionChanged(changes: SimpleChanges) {
return (
changes['processDefinitionName']?.currentValue &&
changes['processDefinitionName'].currentValue !== changes['processDefinitionName'].previousValue
);
}
private _filter(value: string): ProcessDefinitionRepresentation[] {
if (value !== null && value !== undefined) {
const filterValue = value.toLowerCase();
@@ -495,7 +476,7 @@ export class StartProcessInstanceComponent implements OnChanges, OnInit, OnDestr
return [];
}
private formatProcessName(processNameFormat: string, processInstance?: ProcessInstance): string {
private formatProcessName(processNameFormat: string, processInstance?: ProcessInstanceRepresentation): string {
let processName = processNameFormat;
if (processName.match(DATE_TIME_IDENTIFIER_REG_EXP)) {
const presentDateTime = getTime(new Date());

View File

@@ -1,72 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 {
ProcessInstanceQueryRepresentation,
ProcessInstanceFilterRepresentation,
UserProcessInstanceFilterRepresentation
} from '@alfresco/js-api';
export class FilterProcessRepresentationModel implements UserProcessInstanceFilterRepresentation {
appId: number;
filter: ProcessInstanceFilterRepresentation;
icon: string;
id: number;
index: number;
name: string;
recent: boolean;
constructor(obj: any) {
if (obj) {
this.id = obj.id || null;
this.appId = obj.appId || null;
this.name = obj.name || null;
this.recent = !!obj.recent;
this.icon = obj.icon || null;
this.filter = obj.filter || null;
this.index = obj.index;
}
}
hasFilter() {
return !!this.filter;
}
}
/**
* This object represent the parameters of a process filter.
*/
export class ProcessFilterParamRepresentationModel implements ProcessInstanceQueryRepresentation {
processDefinitionId?: string;
processInstanceId?: string;
appDefinitionId?: number;
state?: any;
sort?: any;
page?: number;
size?: number;
constructor(obj?: any) {
this.processDefinitionId = obj.processDefinitionId || null;
this.appDefinitionId = obj.appDefinitionId || null;
this.processInstanceId = obj.processInstanceId || null;
this.state = obj.state || null;
this.sort = obj.sort || null;
this.page = obj.page || null;
this.size = obj.size || null;
}
}

View File

@@ -1,42 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 class ProcessDefinitionRepresentation {
id: string;
name: string;
description: string;
key: string;
category: string;
version: number;
deploymentId: string;
tenantId: string;
metaDataValues: any[];
hasStartForm: boolean;
constructor(obj?: any) {
this.id = obj?.id;
this.name = obj?.name;
this.description = obj?.description;
this.key = obj?.key;
this.category = obj?.category;
this.version = obj?.version || 0;
this.deploymentId = obj?.deploymentId;
this.tenantId = obj?.tenantId;
this.metaDataValues = obj?.metaDataValues || [];
this.hasStartForm = obj?.hasStartForm === true;
}
}

View File

@@ -1,34 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 class ProcessFilterRequestRepresentation {
processDefinitionId: string;
appDefinitionId: string;
state: string;
sort: string;
page: number;
size: number;
constructor(obj?: any) {
this.processDefinitionId = obj?.processDefinitionId;
this.appDefinitionId = obj?.appDefinitionId;
this.state = obj?.state;
this.sort = obj?.sort;
this.page = obj?.page || 0;
this.size = obj?.size || 25;
}
}

View File

@@ -1,33 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { RestVariable } from '@alfresco/js-api';
export class ProcessInstanceVariable implements RestVariable {
name?: string;
scope?: string;
type?: string;
value?: string;
valueUrl?: string;
constructor(obj?: any) {
this.name = obj?.name;
this.scope = obj?.scope;
this.value = obj?.value;
this.valueUrl = obj?.valueUrl;
}
}

View File

@@ -1,58 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { LightUserRepresentation, ProcessInstanceRepresentation, RestVariable } from '@alfresco/js-api';
export class ProcessInstance implements ProcessInstanceRepresentation {
businessKey?: string;
ended?: Date;
graphicalNotationDefined?: boolean;
id?: string;
name?: string;
processDefinitionCategory?: string;
processDefinitionDeploymentId?: string;
processDefinitionDescription?: string;
processDefinitionId?: string;
processDefinitionKey?: string;
processDefinitionName?: string;
processDefinitionVersion?: number;
startFormDefined?: boolean;
started?: Date;
startedBy?: LightUserRepresentation;
tenantId?: string;
variables?: RestVariable[];
constructor(data?: any) {
this.businessKey = data?.businessKey;
this.ended = data?.ended;
this.graphicalNotationDefined = data?.graphicalNotationDefined;
this.id = data?.id;
this.name = data?.name;
this.processDefinitionCategory = data?.processDefinitionCategory;
this.processDefinitionDeploymentId = data?.processDefinitionDeploymentId;
this.processDefinitionDescription = data?.processDefinitionDescription;
this.processDefinitionId = data?.processDefinitionId;
this.processDefinitionKey = data?.processDefinitionKey;
this.processDefinitionName = data?.processDefinitionName;
this.processDefinitionVersion = data?.processDefinitionVersion;
this.startFormDefined = data?.startFormDefined;
this.started = data?.started;
this.startedBy = data?.startedBy;
this.tenantId = data?.tenantId;
this.variables = data?.variables;
}
}

View File

@@ -1,37 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { ProcessInstance } from './process-instance.model';
export class ProcessListModel {
size: number;
total: number;
start: number;
length: number;
data: ProcessInstance [];
constructor(obj?: any) {
if (obj) {
this.size = obj.size || null;
this.total = obj.total || null;
this.start = obj.start || null;
this.length = obj.length || null;
this.data = obj.data || [];
}
}
}

View File

@@ -1,34 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const processPresetsDefaultModel = {
default: [
{
key: 'name',
type: 'text',
title: 'ADF_PROCESS_LIST.PROPERTIES.NAME',
sortable: true
},
{
key: 'created',
type: 'text',
title: 'ADF_PROCESS_LIST.PROPERTIES.CREATED',
cssClass: 'hidden',
sortable: true
}
]
};

View File

@@ -28,11 +28,4 @@ export * from './components/start-process.component';
export * from './services/process.service';
export * from './services/process-filter.service';
// models
export * from './models/filter-process.model';
export * from './models/process-definition.model';
export * from './models/process-instance.model';
export * from './models/process-instance-filter.model';
export * from './models/process-instance-variable.model';
export * from './process-list.module';

View File

@@ -16,22 +16,34 @@
*/
import { TestBed } from '@angular/core/testing';
import {
mockError,
fakeProcessFiltersResponse,
dummyRunningFilter,
dummyAllFilter,
dummyCompletedFilter,
dummyDuplicateRunningFilter
} from '../../mock';
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { ProcessFilterService } from './process-filter.service';
import { CoreTestingModule } from '@alfresco/adf-core';
import { ProcessInstanceFilterRepresentation } from '@alfresco/js-api';
import { ProcessInstanceFilterRepresentation, UserProcessInstanceFilterRepresentation } from '@alfresco/js-api';
import { of } from 'rxjs';
declare let jasmine: any;
const fakeProcessFiltersResponse: any = {
size: 1,
total: 1,
start: 0,
data: [
{
name: 'Running',
appId: '22',
id: 333,
recent: true,
icon: 'glyphicon-random',
filter: { sort: 'created-desc', name: '', state: 'running' }
}
]
};
const mockError = {
message: null,
messageKey: 'GENERAL.ERROR.FORBIDDEN'
};
describe('Process filter', () => {
let service: ProcessFilterService;
@@ -47,9 +59,7 @@ describe('Process filter', () => {
let createFilter: jasmine.Spy;
beforeEach(() => {
getFilters = spyOn(service.userFiltersApi, 'getUserProcessInstanceFilters').and.returnValue(
Promise.resolve(fakeProcessFiltersResponse)
);
getFilters = spyOn(service.userFiltersApi, 'getUserProcessInstanceFilters').and.returnValue(Promise.resolve(fakeProcessFiltersResponse));
jasmine.Ajax.install();
});
@@ -92,7 +102,7 @@ describe('Process filter', () => {
});
it('should return the default filters', (done) => {
service.createDefaultFilters(1234).subscribe((res: FilterProcessRepresentationModel[]) => {
service.createDefaultFilters(1234).subscribe((res) => {
expect(res).toBeDefined();
expect(res.length).toEqual(3);
expect(res[0].name).toEqual('Running');
@@ -142,7 +152,7 @@ describe('Process filter', () => {
});
it('should be able create filters and add sorting information to the response', (done) => {
service.createDefaultFilters(1234).subscribe((res: FilterProcessRepresentationModel[]) => {
service.createDefaultFilters(1234).subscribe((res) => {
expect(res).toBeDefined();
expect(res.length).toEqual(3);
expect(res[0].name).toEqual('Running');
@@ -211,12 +221,12 @@ describe('Process filter', () => {
describe('add filter', () => {
beforeEach(() => {
createFilter = spyOn(service.userFiltersApi, 'createUserProcessInstanceFilter').and.callFake(
(processFilter) => Promise.resolve(processFilter)
createFilter = spyOn(service.userFiltersApi, 'createUserProcessInstanceFilter').and.callFake((processFilter) =>
Promise.resolve(processFilter)
);
});
const filter = fakeProcessFiltersResponse.data[0];
const filter: UserProcessInstanceFilterRepresentation = fakeProcessFiltersResponse.data[0];
it('should call the API to create the filter', () => {
service.addProcessFilter(filter);
@@ -255,7 +265,7 @@ describe('Process filter', () => {
});
describe('isFilterAlreadyExisting', () => {
let dummyProcessFilters: FilterProcessRepresentationModel[];
let dummyProcessFilters: UserProcessInstanceFilterRepresentation[];
let filterRepresentationData: ProcessInstanceFilterRepresentation;
beforeEach(() => {
@@ -267,8 +277,7 @@ describe('Process filter', () => {
id: 8,
index: 0,
name: 'Running',
recent: false,
hasFilter: () => true
recent: false
}
];
@@ -296,10 +305,42 @@ describe('Process filter', () => {
it('should return an array with unique process filters', (done) => {
const appId = 123;
const runningFilter = dummyRunningFilter;
const completedFilter = dummyCompletedFilter;
const allFilter = dummyAllFilter;
const duplicateRunningFilter = dummyDuplicateRunningFilter;
const runningFilter = {
appId: 123,
name: 'Running',
filter: { sort: 'created-desc', name: '', state: 'running' },
icon: 'fa-random',
id: 18,
index: 10,
recent: false
};
const completedFilter = {
appId: 123,
name: 'Completed',
filter: { sort: 'created-desc', name: '', state: 'completed' },
icon: 'fa-random',
id: 19,
index: 11,
recent: false
};
const allFilter = {
appId: 123,
name: 'All',
filter: { sort: 'created-desc', name: '', state: 'all' },
icon: 'fa-random',
id: 20,
index: 12,
recent: false
};
const duplicateRunningFilter = {
appId: 123,
name: 'Running',
filter: { sort: 'created-desc', name: '', state: 'running' },
icon: 'fa-random',
id: 21,
index: 13,
recent: false
};
const runningObservable = of(runningFilter);
const completedObservable = of(completedFilter);
@@ -319,9 +360,9 @@ describe('Process filter', () => {
service.createDefaultFilters(appId).subscribe((result) => {
expect(result).toEqual([
new FilterProcessRepresentationModel({ ...runningFilter, filter: runningFilter.filter, appId }),
new FilterProcessRepresentationModel({ ...completedFilter, filter: completedFilter.filter, appId }),
new FilterProcessRepresentationModel({ ...allFilter, filter: allFilter.filter, appId })
{ ...runningFilter, filter: runningFilter.filter, appId },
{ ...completedFilter, filter: completedFilter.filter, appId },
{ ...allFilter, filter: allFilter.filter, appId }
]);
done();
});

View File

@@ -18,7 +18,6 @@
import { AlfrescoApiService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, forkJoin } from 'rxjs';
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { map } from 'rxjs/operators';
import {
ResultListDataRepresentationUserProcessInstanceFilterRepresentation,
@@ -30,15 +29,13 @@ import {
providedIn: 'root'
})
export class ProcessFilterService {
private _userFiltersApi: UserFiltersApi;
get userFiltersApi(): UserFiltersApi {
this._userFiltersApi = this._userFiltersApi ?? new UserFiltersApi(this.alfrescoApiService.getInstance());
return this._userFiltersApi;
}
constructor(private alfrescoApiService: AlfrescoApiService) {
}
constructor(private alfrescoApiService: AlfrescoApiService) {}
/**
* Gets all filters defined for a Process App.
@@ -46,20 +43,18 @@ export class ProcessFilterService {
* @param appId ID of the target app
* @returns Array of filter details
*/
getProcessFilters(appId: number): Observable<FilterProcessRepresentationModel[]> {
return from(this.callApiProcessFilters(appId))
.pipe(
map((response) => {
const filters: FilterProcessRepresentationModel[] = [];
response.data.forEach((filter) => {
if (!this.isFilterAlreadyExisting(filters, filter.name)) {
const filterModel = new FilterProcessRepresentationModel(filter);
filters.push(filterModel);
}
});
return filters;
})
);
getProcessFilters(appId: number): Observable<UserProcessInstanceFilterRepresentation[]> {
return from(this.callApiProcessFilters(appId)).pipe(
map((response) => {
const filters = [];
response.data.forEach((filter) => {
if (!this.isFilterAlreadyExisting(filters, filter.name)) {
filters.push(filter);
}
});
return filters;
})
);
}
/**
@@ -70,10 +65,7 @@ export class ProcessFilterService {
* @returns Details of the filter
*/
getProcessFilterById(filterId: number, appId?: number): Observable<UserProcessInstanceFilterRepresentation> {
return from(this.callApiProcessFilters(appId))
.pipe(
map((response) => response.data.find((filter) => filter.id === filterId))
);
return from(this.callApiProcessFilters(appId)).pipe(map((response) => response.data.find((filter) => filter.id === filterId)));
}
/**
@@ -84,10 +76,7 @@ export class ProcessFilterService {
* @returns Details of the filter
*/
getProcessFilterByName(filterName: string, appId?: number): Observable<UserProcessInstanceFilterRepresentation> {
return from(this.callApiProcessFilters(appId))
.pipe(
map((response) => response.data.find((filter) => filter.name === filterName))
);
return from(this.callApiProcessFilters(appId)).pipe(map((response) => response.data.find((filter) => filter.name === filterName)));
}
/**
@@ -96,7 +85,7 @@ export class ProcessFilterService {
* @param appId ID of the target app
* @returns Default filters just created
*/
createDefaultFilters(appId: number): Observable<FilterProcessRepresentationModel[]> {
createDefaultFilters(appId: number): Observable<UserProcessInstanceFilterRepresentation[]> {
const runningFilter = this.getRunningFilterInstance(appId, 0);
const runningObservable = this.addProcessFilter(runningFilter);
@@ -107,39 +96,33 @@ export class ProcessFilterService {
const allObservable = this.addProcessFilter(allFilter);
return new Observable((observer) => {
forkJoin([
runningObservable,
completedObservable,
allObservable
]
).subscribe(
(res) => {
const filters: FilterProcessRepresentationModel[] = [];
res.forEach((filter) => {
if (!this.isFilterAlreadyExisting(filters, filter.name)) {
if (filter.name === runningFilter.name) {
filters.push(new FilterProcessRepresentationModel({ ...filter, filter: runningFilter.filter, appId }));
} else if (filter.name === completedFilter.name) {
filters.push(new FilterProcessRepresentationModel({ ...filter, filter: completedFilter.filter, appId }));
} else if (filter.name === allFilter.name) {
filters.push(new FilterProcessRepresentationModel({ ...filter, filter: allFilter.filter, appId }));
}
forkJoin([runningObservable, completedObservable, allObservable]).subscribe((res) => {
const filters: UserProcessInstanceFilterRepresentation[] = [];
res.forEach((filter) => {
if (!this.isFilterAlreadyExisting(filters, filter.name)) {
if (filter.name === runningFilter.name) {
filters.push({ ...filter, filter: runningFilter.filter, appId });
} else if (filter.name === completedFilter.name) {
filters.push({ ...filter, filter: completedFilter.filter, appId });
} else if (filter.name === allFilter.name) {
filters.push({ ...filter, filter: allFilter.filter, appId });
}
});
observer.next(filters);
observer.complete();
}
});
observer.next(filters);
observer.complete();
});
});
}
/**
* Checks if a filter with the given name already exists in the list of filters.
*
* @param filters - An array of FilterProcessRepresentationModel objects representing the existing filters.
* @param filters - An array of objects representing the existing filters.
* @param filterName - The name of the filter to check for existence.
* @returns - True if a filter with the specified name already exists, false otherwise.
*/
isFilterAlreadyExisting(filters: FilterProcessRepresentationModel[], filterName: string): boolean {
isFilterAlreadyExisting(filters: Partial<{ name: string }>[], filterName: string): boolean {
return filters.some((existingFilter) => existingFilter.name === filterName);
}
@@ -150,15 +133,15 @@ export class ProcessFilterService {
* @param index of the filter (optional)
* @returns Filter just created
*/
getRunningFilterInstance(appId: number, index?: number): FilterProcessRepresentationModel {
return new FilterProcessRepresentationModel({
getRunningFilterInstance(appId: number, index?: number): UserProcessInstanceFilterRepresentation {
return {
name: 'Running',
appId,
recent: true,
icon: 'glyphicon-random',
filter: { sort: 'created-desc', name: '', state: 'running' },
index
});
};
}
/**
@@ -167,7 +150,7 @@ export class ProcessFilterService {
* @param filter The filter to add
* @returns The filter just added
*/
addProcessFilter(filter: FilterProcessRepresentationModel): Observable<UserProcessInstanceFilterRepresentation> {
addProcessFilter(filter: UserProcessInstanceFilterRepresentation): Observable<UserProcessInstanceFilterRepresentation> {
return from(this.userFiltersApi.createUserProcessInstanceFilter(filter));
}
@@ -185,25 +168,25 @@ export class ProcessFilterService {
}
}
getCompletedFilterInstance(appId: number, index?: number): FilterProcessRepresentationModel {
return new FilterProcessRepresentationModel({
getCompletedFilterInstance(appId: number, index?: number): UserProcessInstanceFilterRepresentation {
return {
name: 'Completed',
appId,
recent: false,
icon: 'glyphicon-ok-sign',
filter: { sort: 'created-desc', name: '', state: 'completed' },
index
});
};
}
getAllFilterInstance(appId: number, index?: number): FilterProcessRepresentationModel {
return new FilterProcessRepresentationModel({
getAllFilterInstance(appId: number, index?: number): UserProcessInstanceFilterRepresentation {
return {
name: 'All',
appId,
recent: true,
icon: 'glyphicon-th',
filter: { sort: 'created-desc', name: '', state: 'all' },
index
});
};
}
}

View File

@@ -16,39 +16,62 @@
*/
import { TestBed } from '@angular/core/testing';
import { exampleProcess, mockError, fakeProcessDef, fakeTasksList } from '../../mock';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { exampleProcess } from '../../mock';
import { ProcessService } from './process.service';
import { CoreModule, DateFnsUtils } from '@alfresco/adf-core';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { ProcessInstanceQueryRepresentation, ProcessDefinitionRepresentation, RestVariable } from '@alfresco/js-api';
import { TaskDetailsModel } from '@alfresco/adf-process-services';
const fakeTasksList = {
data: [
new TaskDetailsModel({
id: 1,
name: 'Task 1',
processInstanceId: 1000,
created: '2016-11-10T03:37:30.010+0000'
}),
new TaskDetailsModel({
id: 2,
name: 'Task 2',
processInstanceId: 1000,
created: '2016-11-10T03:37:30.010+0000'
})
]
};
const fakeProcessDef: ProcessDefinitionRepresentation = {
id: '32323',
key: 'blah',
name: 'Process 1'
};
const mockError = {
message: null,
messageKey: 'GENERAL.ERROR.FORBIDDEN'
};
describe('ProcessService', () => {
let service: ProcessService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
ProcessTestingModule
]
imports: [CoreModule.forRoot(), ProcessTestingModule]
});
service = TestBed.inject(ProcessService);
});
describe('process instances', () => {
const filter = new ProcessFilterParamRepresentationModel({
const filter: ProcessInstanceQueryRepresentation = {
processDefinitionId: '1',
appDefinitionId: '1',
appDefinitionId: 1,
page: 1,
sort: 'created-asc',
state: 'completed'
});
};
beforeEach(() => {
spyOn(service.processInstancesApi, 'getProcessInstances')
.and
.returnValue(Promise.resolve({ data: [exampleProcess] }));
spyOn(service.processInstancesApi, 'getProcessInstances').and.returnValue(Promise.resolve({ data: [exampleProcess] }));
});
it('should return the correct number of instances', (done) => {
@@ -70,13 +93,10 @@ describe('ProcessService', () => {
});
describe('process instance', () => {
const processId = 'test';
beforeEach(() => {
spyOn(service.processInstancesApi, 'getProcessInstance')
.and
.returnValue(Promise.resolve(exampleProcess));
spyOn(service.processInstancesApi, 'getProcessInstance').and.returnValue(Promise.resolve(exampleProcess));
});
it('should return the correct instance data', (done) => {
@@ -95,9 +115,7 @@ describe('ProcessService', () => {
let startNewProcessInstance: jasmine.Spy;
beforeEach(() => {
startNewProcessInstance = spyOn(service.processInstancesApi, 'startNewProcessInstance')
.and
.returnValue(Promise.resolve(exampleProcess));
startNewProcessInstance = spyOn(service.processInstancesApi, 'startNewProcessInstance').and.returnValue(Promise.resolve(exampleProcess));
});
it('should call the API to create the process instance', () => {
@@ -135,8 +153,7 @@ describe('ProcessService', () => {
startNewProcessInstance = startNewProcessInstance.and.returnValue(Promise.reject(mockError));
service.startProcess(processDefId, processName).subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -147,8 +164,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
startNewProcessInstance = startNewProcessInstance.and.returnValue(Promise.reject(new Error('Server error')));
service.startProcess(processDefId, processName).subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -158,14 +174,11 @@ describe('ProcessService', () => {
});
describe('cancel process instance', () => {
const processInstanceId = '1234';
let deleteProcessInstance: jasmine.Spy;
beforeEach(() => {
deleteProcessInstance = spyOn(service.processInstancesApi, 'deleteProcessInstance')
.and
.returnValue(Promise.resolve());
deleteProcessInstance = spyOn(service.processInstancesApi, 'deleteProcessInstance').and.returnValue(Promise.resolve());
});
it('should call service to delete process instances', () => {
@@ -181,8 +194,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
deleteProcessInstance = deleteProcessInstance.and.returnValue(Promise.reject(mockError));
service.cancelProcess(null).subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -193,8 +205,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
deleteProcessInstance = deleteProcessInstance.and.returnValue(Promise.reject(new Error('Server error')));
service.cancelProcess(null).subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -204,13 +215,12 @@ describe('ProcessService', () => {
});
describe('process definitions', () => {
let getProcessDefinitions: jasmine.Spy;
beforeEach(() => {
getProcessDefinitions = spyOn(service.processDefinitionsApi, 'getProcessDefinitions')
.and
.returnValue(Promise.resolve({ data: [fakeProcessDef, fakeProcessDef] }));
getProcessDefinitions = spyOn(service.processDefinitionsApi, 'getProcessDefinitions').and.returnValue(
Promise.resolve({ data: [fakeProcessDef, fakeProcessDef] })
);
});
it('should return the correct number of process defs', (done) => {
@@ -241,8 +251,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
getProcessDefinitions = getProcessDefinitions.and.returnValue(Promise.reject(mockError));
service.getProcessDefinitions().subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -253,8 +262,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
getProcessDefinitions = getProcessDefinitions.and.returnValue(Promise.reject(new Error('Server error')));
service.getProcessDefinitions().subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -264,14 +272,11 @@ describe('ProcessService', () => {
});
describe('process instance tasks', () => {
const processId = '1001';
let listTasks: jasmine.Spy;
beforeEach(() => {
listTasks = spyOn(service.tasksApi, 'listTasks')
.and
.returnValue(Promise.resolve(fakeTasksList));
listTasks = spyOn(service.tasksApi, 'listTasks').and.returnValue(Promise.resolve(fakeTasksList));
});
it('should return the correct number of tasks', (done) => {
@@ -315,8 +320,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
listTasks = listTasks.and.returnValue(Promise.reject(mockError));
service.getProcessTasks(processId).subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -327,8 +331,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
listTasks = listTasks.and.returnValue(Promise.reject(new Error('Server error')));
service.getProcessTasks(processId).subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -338,29 +341,35 @@ describe('ProcessService', () => {
});
describe('process variables', () => {
let getVariablesSpy: jasmine.Spy;
let createOrUpdateProcessInstanceVariablesSpy: jasmine.Spy;
let deleteProcessInstanceVariableSpy: jasmine.Spy;
beforeEach(() => {
getVariablesSpy = spyOn(service.processInstanceVariablesApi, 'getProcessInstanceVariables').and.returnValue(Promise.resolve([{
name: 'var1',
value: 'Test1'
}, {
name: 'var3',
value: 'Test3'
}]));
getVariablesSpy = spyOn(service.processInstanceVariablesApi, 'getProcessInstanceVariables').and.returnValue(
Promise.resolve([
{
name: 'var1',
value: 'Test1'
},
{
name: 'var3',
value: 'Test3'
}
])
);
createOrUpdateProcessInstanceVariablesSpy = spyOn(service.processInstanceVariablesApi,
'createOrUpdateProcessInstanceVariables').and.returnValue(Promise.resolve({} as any));
createOrUpdateProcessInstanceVariablesSpy = spyOn(
service.processInstanceVariablesApi,
'createOrUpdateProcessInstanceVariables'
).and.returnValue(Promise.resolve({} as any));
deleteProcessInstanceVariableSpy = spyOn(service.processInstanceVariablesApi,
'deleteProcessInstanceVariable').and.returnValue(Promise.resolve());
deleteProcessInstanceVariableSpy = spyOn(service.processInstanceVariablesApi, 'deleteProcessInstanceVariable').and.returnValue(
Promise.resolve()
);
});
describe('get variables', () => {
it('should call service to fetch variables', () => {
service.getProcessInstanceVariables(null);
expect(getVariablesSpy).toHaveBeenCalled();
@@ -369,8 +378,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
getVariablesSpy = getVariablesSpy.and.returnValue(Promise.reject(mockError));
service.getProcessInstanceVariables(null).subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -381,8 +389,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
getVariablesSpy = getVariablesSpy.and.returnValue(Promise.reject(new Error('Server error')));
service.getProcessInstanceVariables(null).subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -392,13 +399,16 @@ describe('ProcessService', () => {
});
describe('create or update variables', () => {
const updatedVariables = [new ProcessInstanceVariable({
name: 'var1',
value: 'Test1'
}), new ProcessInstanceVariable({
name: 'var3',
value: 'Test3'
})];
const updatedVariables: RestVariable[] = [
{
name: 'var1',
value: 'Test1'
},
{
name: 'var3',
value: 'Test3'
}
];
it('should call service to create or update variables', () => {
service.createOrUpdateProcessInstanceVariables('123', updatedVariables);
@@ -408,8 +418,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
createOrUpdateProcessInstanceVariablesSpy = createOrUpdateProcessInstanceVariablesSpy.and.returnValue(Promise.reject(mockError));
service.createOrUpdateProcessInstanceVariables('123', updatedVariables).subscribe(
() => {
},
() => {},
(err) => {
expect(err).toBe(mockError);
done();
@@ -418,10 +427,11 @@ describe('ProcessService', () => {
});
it('should return a default error if no data is returned by the API', (done) => {
createOrUpdateProcessInstanceVariablesSpy = createOrUpdateProcessInstanceVariablesSpy.and.returnValue(Promise.reject(new Error('Server error')));
createOrUpdateProcessInstanceVariablesSpy = createOrUpdateProcessInstanceVariablesSpy.and.returnValue(
Promise.reject(new Error('Server error'))
);
service.createOrUpdateProcessInstanceVariables('123', updatedVariables).subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();
@@ -434,8 +444,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', (done) => {
deleteProcessInstanceVariableSpy = deleteProcessInstanceVariableSpy.and.returnValue(Promise.reject(mockError));
service.deleteProcessInstanceVariable('123', 'myVar').subscribe(
() => {
},
() => {},
(res) => {
expect(res).toBe(mockError);
done();
@@ -446,8 +455,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', (done) => {
deleteProcessInstanceVariableSpy = deleteProcessInstanceVariableSpy.and.returnValue(Promise.reject(new Error('Server error')));
service.deleteProcessInstanceVariable('123', 'myVar').subscribe(
() => {
},
() => {},
(err) => {
expect(err.message).toBe('Server error');
done();

View File

@@ -18,54 +18,48 @@
import { AlfrescoApiService, DateFnsUtils, FormValues } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import {
TasksApi,
FormDefinitionRepresentation,
ProcessDefinitionsApi,
ProcessInstancesApi,
RestVariable,
ProcessInstanceAuditInfoRepresentation,
ProcessInstanceQueryRepresentation,
ProcessInstanceRepresentation,
ProcessInstanceVariablesApi
ProcessInstancesApi,
ProcessInstanceVariablesApi,
RestVariable,
ResultListDataRepresentationProcessInstanceRepresentation,
TasksApi,
ProcessDefinitionRepresentation
} from '@alfresco/js-api';
import { Observable, from, throwError, of } from 'rxjs';
import { from, Observable } from 'rxjs';
import { TaskDetailsModel } from '../../task-list';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { ProcessDefinitionRepresentation } from '../models/process-definition.model';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessInstance } from '../models/process-instance.model';
import { ProcessListModel } from '../models/process-list.model';
import { map, catchError } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { DatePipe } from '@angular/common';
@Injectable({
providedIn: 'root'
})
export class ProcessService {
private _tasksApi: TasksApi;
get tasksApi(): TasksApi {
this._tasksApi = this._tasksApi ?? new TasksApi(this.alfrescoApiService.getInstance());
return this._tasksApi;
return (this._tasksApi ||= new TasksApi(this.alfrescoApiService.getInstance()));
}
private _processDefinitionsApi: ProcessDefinitionsApi;
get processDefinitionsApi(): ProcessDefinitionsApi {
this._processDefinitionsApi = this._processDefinitionsApi ?? new ProcessDefinitionsApi(this.alfrescoApiService.getInstance());
return this._processDefinitionsApi;
return (this._processDefinitionsApi ||= new ProcessDefinitionsApi(this.alfrescoApiService.getInstance()));
}
private _processInstancesApi: ProcessInstancesApi;
get processInstancesApi(): ProcessInstancesApi {
this._processInstancesApi = this._processInstancesApi ?? new ProcessInstancesApi(this.alfrescoApiService.getInstance());
return this._processInstancesApi;
return (this._processInstancesApi ||= new ProcessInstancesApi(this.alfrescoApiService.getInstance()));
}
private _processInstanceVariablesApi: ProcessInstanceVariablesApi;
get processInstanceVariablesApi(): ProcessInstanceVariablesApi {
this._processInstanceVariablesApi = this._processInstanceVariablesApi ?? new ProcessInstanceVariablesApi(this.alfrescoApiService.getInstance());
return this._processInstanceVariablesApi;
return (this._processInstanceVariablesApi ||= new ProcessInstanceVariablesApi(this.alfrescoApiService.getInstance()));
}
constructor(private alfrescoApiService: AlfrescoApiService) {
}
constructor(private alfrescoApiService: AlfrescoApiService) {}
/**
* Gets process instances for a filter and optionally a process definition.
@@ -74,20 +68,18 @@ export class ProcessService {
* @param processDefinitionKey Limits returned instances to a process definition
* @returns List of process instances
*/
getProcessInstances(requestNode: ProcessFilterParamRepresentationModel, processDefinitionKey?: string): Observable<ProcessListModel> {
return from(this.processInstancesApi.getProcessInstances(requestNode))
.pipe(
map((res: any) => {
if (processDefinitionKey) {
const filtered = res.data.filter((process) => process.processDefinitionKey === processDefinitionKey);
res.data = filtered;
return res;
} else {
return res;
}
}),
catchError((err) => this.handleProcessError(err))
);
getProcessInstances(
requestNode: ProcessInstanceQueryRepresentation,
processDefinitionKey?: string
): Observable<ResultListDataRepresentationProcessInstanceRepresentation> {
return from(this.processInstancesApi.getProcessInstances(requestNode)).pipe(
map((res) => {
if (processDefinitionKey) {
res.data = res.data.filter((process) => process.processDefinitionKey === processDefinitionKey);
}
return res;
})
);
}
/**
@@ -97,18 +89,19 @@ export class ProcessService {
* @param processDefinitionKey Limits returned instances to a process definition
* @returns List of processes
*/
getProcesses(requestNode: ProcessFilterParamRepresentationModel, processDefinitionKey?: string): Observable<ProcessListModel> {
return this.getProcessInstances(requestNode, processDefinitionKey)
.pipe(
map(response => ({
...response,
data: (response.data || []).map(instance => {
instance.name = this.getProcessNameOrDescription(instance, 'medium');
return instance;
})
})),
catchError(() => of(new ProcessListModel({})))
);
getProcesses(
requestNode: ProcessInstanceQueryRepresentation,
processDefinitionKey?: string
): Observable<ResultListDataRepresentationProcessInstanceRepresentation> {
return this.getProcessInstances(requestNode, processDefinitionKey).pipe(
map((response) => {
response.data = (response.data || []).map((instance) => {
instance.name = this.getProcessNameOrDescription(instance, 'medium');
return instance;
});
return response;
})
);
}
/**
@@ -118,10 +111,7 @@ export class ProcessService {
* @returns Binary PDF data
*/
fetchProcessAuditPdfById(processId: string): Observable<Blob> {
return from(this.processInstancesApi.getProcessAuditPdf(processId))
.pipe(
catchError((err) => this.handleProcessError(err))
);
return from(this.processInstancesApi.getProcessAuditPdf(processId));
}
/**
@@ -130,11 +120,8 @@ export class ProcessService {
* @param processId ID of the target process
* @returns JSON data
*/
fetchProcessAuditJsonById(processId: string): Observable<any> {
return from(this.processInstancesApi.getTaskAuditLog(processId))
.pipe(
catchError((err) => this.handleProcessError(err))
);
fetchProcessAuditJsonById(processId: string): Observable<ProcessInstanceAuditInfoRepresentation> {
return from(this.processInstancesApi.getTaskAuditLog(processId));
}
/**
@@ -143,12 +130,8 @@ export class ProcessService {
* @param processInstanceId ID of the target process
* @returns Metadata for the instance
*/
getProcess(processInstanceId: string): Observable<ProcessInstance> {
return from(this.processInstancesApi.getProcessInstance(processInstanceId))
.pipe(
map(this.toJson),
catchError((err) => this.handleProcessError(err))
);
getProcess(processInstanceId: string): Observable<ProcessInstanceRepresentation> {
return from(this.processInstancesApi.getProcessInstance(processInstanceId)).pipe(map(this.toJson));
}
/**
@@ -158,36 +141,26 @@ export class ProcessService {
* @returns Form definition
*/
getStartFormDefinition(processId: string): Observable<any> {
return from(this.processDefinitionsApi.getProcessDefinitionStartForm(processId))
.pipe(
map(this.toJson),
catchError((err) => this.handleProcessError(err))
);
return from(this.processDefinitionsApi.getProcessDefinitionStartForm(processId)).pipe(map(this.toJson));
}
/**
* Gets the start form instance for a given process.
*
* @param processId Process definition ID
* @returns Form definition
*/
getStartFormInstance(processId: string): Observable<any> {
return from(this.processInstancesApi.getProcessInstanceStartForm(processId))
.pipe(
map(this.toJson),
catchError((err) => this.handleProcessError(err))
);
getStartFormInstance(processId: string): Observable<FormDefinitionRepresentation> {
return from(this.processInstancesApi.getProcessInstanceStartForm(processId)).pipe(map(this.toJson));
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
private toJson(res: any) {
if (res) {
return res || {};
}
@@ -202,21 +175,23 @@ export class ProcessService {
* @returns Array of task instance details
*/
getProcessTasks(processInstanceId: string, state?: string): Observable<TaskDetailsModel[]> {
const taskOpts = state ? {
processInstanceId,
state
} : {
processInstanceId
};
return from(this.tasksApi.listTasks(taskOpts))
.pipe(
map(this.extractData),
map((tasks) => tasks.map((task: any) => {
const taskOpts = state
? {
processInstanceId,
state
}
: {
processInstanceId
};
return from(this.tasksApi.listTasks(taskOpts)).pipe(
map(this.extractData),
map((tasks) =>
tasks.map((task: any) => {
task.created = DateFnsUtils.formatDate(new Date(task.created), 'YYYY-MM-DD');
return task;
})),
catchError((err) => this.handleProcessError(err))
);
})
)
);
}
/**
@@ -226,20 +201,15 @@ export class ProcessService {
* @returns Array of process definitions
*/
getProcessDefinitions(appId?: number): Observable<ProcessDefinitionRepresentation[]> {
const opts = appId ? {
latest: true,
appDefinitionId: appId
} : {
latest: true
};
return from(
this.processDefinitionsApi.getProcessDefinitions(opts)
)
.pipe(
map(this.extractData),
map((processDefs) => processDefs.map((pd) => new ProcessDefinitionRepresentation(pd))),
catchError((err) => this.handleProcessError(err))
);
const opts = appId
? {
latest: true,
appDefinitionId: appId
}
: {
latest: true
};
return from(this.processDefinitionsApi.getProcessDefinitions(opts)).pipe(map(this.extractData));
}
/**
@@ -252,7 +222,13 @@ export class ProcessService {
* @param variables Array of process instance variables
* @returns Details of the process instance just started
*/
startProcess(processDefinitionId: string, name: string, outcome?: string, startFormValues?: FormValues, variables?: ProcessInstanceVariable[]): Observable<ProcessInstance> {
startProcess(
processDefinitionId: string,
name: string,
outcome?: string,
startFormValues?: FormValues,
variables?: RestVariable[]
): Observable<ProcessInstanceRepresentation> {
const startRequest: any = {
name,
processDefinitionId
@@ -266,13 +242,7 @@ export class ProcessService {
if (variables) {
startRequest.variables = variables;
}
return from(
this.processInstancesApi.startNewProcessInstance(startRequest)
)
.pipe(
map((pd) => new ProcessInstance(pd)),
catchError((err) => this.handleProcessError(err))
);
return from(this.processInstancesApi.startNewProcessInstance(startRequest));
}
/**
@@ -282,12 +252,7 @@ export class ProcessService {
* @returns Null response notifying when the operation is complete
*/
cancelProcess(processInstanceId: string): Observable<void> {
return from(
this.processInstancesApi.deleteProcessInstance(processInstanceId)
)
.pipe(
catchError((err) => this.handleProcessError(err))
);
return from(this.processInstancesApi.deleteProcessInstance(processInstanceId));
}
/**
@@ -296,14 +261,8 @@ export class ProcessService {
* @param processInstanceId ID of the target process
* @returns Array of instance variable info
*/
getProcessInstanceVariables(processInstanceId: string): Observable<ProcessInstanceVariable[]> {
return from(
this.processInstanceVariablesApi.getProcessInstanceVariables(processInstanceId)
)
.pipe(
map((processVars: any[]) => processVars.map((currentProcessVar) => new ProcessInstanceVariable(currentProcessVar))),
catchError((err) => this.handleProcessError(err))
);
getProcessInstanceVariables(processInstanceId: string): Observable<RestVariable[]> {
return from(this.processInstanceVariablesApi.getProcessInstanceVariables(processInstanceId));
}
/**
@@ -313,12 +272,8 @@ export class ProcessService {
* @param variables Variables to update
* @returns Array of instance variable info
*/
createOrUpdateProcessInstanceVariables(processInstanceId: string, variables: RestVariable[]): Observable<ProcessInstanceVariable[]> {
return from(
this.processInstanceVariablesApi.createOrUpdateProcessInstanceVariables(processInstanceId, variables)
).pipe(
catchError((err) => this.handleProcessError(err))
);
createOrUpdateProcessInstanceVariables(processInstanceId: string, variables: RestVariable[]): Observable<RestVariable[]> {
return from(this.processInstanceVariablesApi.createOrUpdateProcessInstanceVariables(processInstanceId, variables));
}
/**
@@ -329,28 +284,18 @@ export class ProcessService {
* @returns Null response notifying when the operation is complete
*/
deleteProcessInstanceVariable(processInstanceId: string, variableName: string): Observable<void> {
return from(
this.processInstanceVariablesApi.deleteProcessInstanceVariable(processInstanceId, variableName)
)
.pipe(
catchError((err) => this.handleProcessError(err))
);
return from(this.processInstanceVariablesApi.deleteProcessInstanceVariable(processInstanceId, variableName));
}
private extractData(res: any) {
return res.data || {};
}
private handleProcessError(error: any) {
return throwError(error || 'Server error');
}
private getProcessNameOrDescription(processInstance: ProcessInstanceRepresentation, dateFormat: string): string {
let name = '';
if (processInstance) {
name = processInstance.name ||
processInstance.processDefinitionName + ' - ' + this.getFormatDate(processInstance.started, dateFormat);
name = processInstance.name || processInstance.processDefinitionName + ' - ' + this.getFormatDate(processInstance.started, dateFormat);
}
return name;

View File

@@ -18,15 +18,13 @@
import { CoreTestingModule, UserInfoMode } from '@alfresco/adf-core';
import { fakeEcmUser, fakeEcmUserNoImage } from '@alfresco/adf-content-services';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { By } from '@angular/platform-browser';
import { BpmUserModel } from '../common/models/bpm-user.model';
import { ProcessUserInfoComponent } from './process-user-info.component';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatTabGroupHarness, MatTabHarness } from '@angular/material/tabs/testing';
const fakeBpmUser = new BpmUserModel({
const fakeBpmUser: any = {
apps: [],
capabilities: null,
company: 'fake-company',
@@ -46,7 +44,7 @@ const fakeBpmUser = new BpmUserModel({
tenantName: 'fake-tenant-name',
tenantPictureId: 'fake-tenant-picture-id',
type: 'fake-type'
});
};
describe('ProcessUserInfoComponent', () => {
const profilePictureUrl = 'alfresco-logo.svg';
@@ -71,7 +69,7 @@ describe('ProcessUserInfoComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule, MatMenuModule]
imports: [CoreTestingModule, ProcessUserInfoComponent]
});
fixture = TestBed.createComponent(ProcessUserInfoComponent);
component = fixture.componentInstance;
@@ -122,11 +120,10 @@ describe('ProcessUserInfoComponent', () => {
});
it('should show last name if first name is null', async () => {
const wrongBpmUser: BpmUserModel = new BpmUserModel({
component.bpmUser = {
firstName: null,
lastName: 'fake-last-name'
});
component.bpmUser = wrongBpmUser;
} as any;
await whenFixtureReady();
const fullNameElement = element.querySelector('#adf-userinfo-bpm-name-display');
fixture.detectChanges();

View File

@@ -15,22 +15,28 @@
* limitations under the License.
*/
import { UserInfoMode } from '@alfresco/adf-core';
import { FullNamePipe, InitialUsernamePipe, UserInfoMode } from '@alfresco/adf-core';
import { EcmUserModel, PeopleContentService } from '@alfresco/adf-content-services';
import { Component, Input, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { MatMenuModule, MatMenuTrigger, MenuPositionX, MenuPositionY } from '@angular/material/menu';
import { Subject } from 'rxjs';
import { PeopleProcessService } from '../common/services/people-process.service';
import { BpmUserModel } from '../common/models/bpm-user.model';
import { UserRepresentation } from '@alfresco/js-api';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatTabsModule } from '@angular/material/tabs';
import { TranslateModule } from '@ngx-translate/core';
import { MatCardModule } from '@angular/material/card';
@Component({
selector: 'adf-process-user-info',
standalone: true,
imports: [CommonModule, FullNamePipe, MatButtonModule, MatMenuModule, InitialUsernamePipe, MatTabsModule, TranslateModule, MatCardModule],
templateUrl: './process-user-info.component.html',
styleUrls: ['./process-user-info.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ProcessUserInfoComponent implements OnDestroy {
@ViewChild(MatMenuTrigger) trigger: MatMenuTrigger;
/** Determines if user is logged in. */
@@ -39,7 +45,7 @@ export class ProcessUserInfoComponent implements OnDestroy {
/** BPM user info. */
@Input()
bpmUser: BpmUserModel;
bpmUser: UserRepresentation;
/** ECM user info. */
@Input()
@@ -80,8 +86,7 @@ export class ProcessUserInfoComponent implements OnDestroy {
private destroy$ = new Subject();
constructor(private peopleProcessService: PeopleProcessService, private peopleContentService: PeopleContentService) {
}
constructor(private peopleProcessService: PeopleProcessService, private peopleContentService: PeopleContentService) {}
ngOnDestroy(): void {
this.destroy$.next(true);

View File

@@ -16,28 +16,10 @@
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProcessUserInfoComponent } from './process-user-info.component';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
import { TranslateModule } from '@ngx-translate/core';
import { FullNamePipe, InitialUsernamePipe, PipeModule } from '@alfresco/adf-core';
@NgModule({
declarations: [ProcessUserInfoComponent],
imports: [
CommonModule,
MatButtonModule,
MatMenuModule,
MatTabsModule,
MatCardModule,
TranslateModule,
PipeModule,
FullNamePipe,
InitialUsernamePipe
],
imports: [ProcessUserInfoComponent],
exports: [ProcessUserInfoComponent]
})
export class ProcessUserInfoModule {}

View File

@@ -18,7 +18,7 @@
import { CommonModule } from '@angular/common';
import { NgModule, ModuleWithProviders } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { CoreModule, FormRenderingService, provideTranslations } from '@alfresco/adf-core';
import { CoreModule, EmptyContentComponent, FormRenderingService, provideTranslations } from '@alfresco/adf-core';
import { MaterialModule } from './material.module';
@@ -48,7 +48,8 @@ import { ProcessUserInfoModule } from './process-user-info/process-user-info.mod
ProcessUserInfoModule,
AttachmentModule,
PeopleModule,
FormModule
FormModule,
EmptyContentComponent
],
providers: [provideTranslations('adf-process-services', 'assets/adf-process-services')],
exports: [

View File

@@ -15,4 +15,6 @@
* limitations under the License.
*/
export * from './public-api';
export * from './task-comments.component';
export * from './services/task-comments.service';
export * from './task-comments.module';

View File

@@ -1,32 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const fakeUser1 = { id: 1, email: 'fake-email@dom.com', firstName: 'firstName', lastName: 'lastName' };
export const fakeUser2 = { id: 1001, email: 'some-one@somegroup.com', firstName: 'some', lastName: 'one' };
export const fakeTasksComment = {
size: 2, total: 2, start: 0,
data: [
{
id: 1, message: 'fake-message-1', created: '', createdBy: fakeUser1
},
{
id: 2, message: 'fake-message-2', created: '', createdBy: fakeUser1
}
]
};

View File

@@ -1,22 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 './task-comments.component';
export * from './services/task-comments.service';
export * from './task-comments.module';

View File

@@ -1,84 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* 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 { TestBed } from '@angular/core/testing';
import { CommentModel, CoreTestingModule } from '@alfresco/adf-core';
import { fakeTasksComment, fakeUser1 } from '../mocks/task-comments.mock';
import { TaskCommentsService } from './task-comments.service';
declare let jasmine: any;
describe('TaskCommentsService', () => {
let service: TaskCommentsService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CoreTestingModule]
});
service = TestBed.inject(TaskCommentsService);
});
beforeEach(() => {
jasmine.Ajax.install();
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
describe('Task comments', () => {
it('should add a comment task ', (done) => {
service.add('999', 'fake-comment-message').subscribe((res: CommentModel) => {
expect(res).toBeDefined();
expect(res.id).not.toEqual(null);
expect(res.message).toEqual('fake-comment-message');
expect(res.created).not.toEqual(null);
expect(res.createdBy.email).toEqual('fake-email@dom.com');
expect(res.createdBy.firstName).toEqual('firstName');
expect(res.createdBy.lastName).toEqual('lastName');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({
id: '111',
message: 'fake-comment-message',
createdBy: fakeUser1,
created: '2016-07-15T11:19:17.440+0000'
})
});
});
it('should return the tasks comments ', (done) => {
service.get('999').subscribe((res: CommentModel[]) => {
expect(res).toBeDefined();
expect(res.length).toEqual(2);
expect(res[0].message).toEqual('fake-message-1');
expect(res[1].message).toEqual('fake-message-2');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(fakeTasksComment)
});
});
});
});

View File

@@ -15,12 +15,11 @@
* limitations under the License.
*/
import { AlfrescoApiService, CommentModel, CommentsService, User } from '@alfresco/adf-core';
import { ActivitiCommentsApi, CommentRepresentation } from '@alfresco/js-api';
import { AlfrescoApiService, CommentModel, CommentsService } from '@alfresco/adf-core';
import { ActivitiCommentsApi } from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { UserProcessModel } from '../../common/models/user-process.model';
import { PeopleProcessService } from '../../common/services/people-process.service';
@Injectable({
@@ -42,17 +41,7 @@ export class TaskCommentsService implements CommentsService {
* @returns Details for each comment
*/
get(id: string): Observable<CommentModel[]> {
return from(this.commentsApi.getTaskComments(id)).pipe(
map((response) => {
const comments: CommentModel[] = [];
response.data.forEach((comment) => {
this.addToComments(comments, comment);
});
return comments;
})
);
return from(this.commentsApi.getTaskComments(id)).pipe(map((response) => response.data.map((comment) => new CommentModel(comment))));
}
/**
@@ -63,32 +52,10 @@ export class TaskCommentsService implements CommentsService {
* @returns Details about the comment
*/
add(id: string, message: string): Observable<CommentModel> {
return from(this.commentsApi.addTaskComment({ message }, id)).pipe(map((response) => this.newCommentModel(response)));
return from(this.commentsApi.addTaskComment({ message }, id)).pipe(map((response) => new CommentModel(response)));
}
private addToComments(comments: CommentModel[], comment: CommentRepresentation): void {
const user = new UserProcessModel(comment.createdBy);
const newComment: CommentRepresentation = {
id: comment.id,
message: comment.message,
created: comment.created,
createdBy: user
};
comments.push(this.newCommentModel(newComment));
}
private newCommentModel(representation: CommentRepresentation): CommentModel {
return new CommentModel({
id: representation.id,
message: representation.message,
created: representation.created,
createdBy: new User(representation.createdBy)
});
}
getUserImage(user: UserProcessModel): string {
return this.peopleProcessService.getUserImage(user);
getUserImage(userId: string): string {
return this.peopleProcessService.getUserImage(userId);
}
}

View File

@@ -16,17 +16,22 @@
*/
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { ADF_COMMENTS_SERVICE } from '@alfresco/adf-core';
import { ADF_COMMENTS_SERVICE, CommentsModule } from '@alfresco/adf-core';
import { TaskCommentsService } from './services/task-comments.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'adf-task-comments',
standalone: true,
imports: [CommonModule, CommentsModule],
templateUrl: './task-comments.component.html',
encapsulation: ViewEncapsulation.None,
providers: [{
provide: ADF_COMMENTS_SERVICE,
useClass: TaskCommentsService
}]
providers: [
{
provide: ADF_COMMENTS_SERVICE,
useClass: TaskCommentsService
}
]
})
export class TaskCommentsComponent {
/** The numeric ID of the task. */

View File

@@ -16,16 +16,10 @@
*/
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TaskCommentsComponent } from './task-comments.component';
import { CoreModule } from '@alfresco/adf-core';
@NgModule({
imports: [
CommonModule,
CoreModule
],
declarations: [TaskCommentsComponent],
imports: [TaskCommentsComponent],
exports: [TaskCommentsComponent]
})
export class TaskCommentsModule {}

View File

@@ -57,7 +57,7 @@
</div>
</mat-form-field>
<people-widget
(peopleSelected)="getAssigneeId($event)"
(peopleSelected)="setAssigneeId($event)"
[field]="field"
class="adf-people-widget-content"></people-widget>
</div>

View File

@@ -247,7 +247,7 @@ describe('StartTaskComponent', () => {
component.taskForm.controls['name'].setValue('fakeName');
component.taskForm.controls['formKey'].setValue(1204);
component.appId = 42;
component.getAssigneeId(testUser.id);
component.setAssigneeId(testUser.id);
fixture.detectChanges();
const createTaskButton = element.querySelector<HTMLElement>('#button-start');
createTaskButton.click();

View File

@@ -24,7 +24,6 @@ import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service';
import { switchMap, defaultIfEmpty, takeUntil } from 'rxjs/operators';
import { UntypedFormBuilder, AbstractControl, Validators, UntypedFormGroup, UntypedFormControl } from '@angular/forms';
import { UserProcessModel } from '../../common/models/user-process.model';
import { isValid } from 'date-fns';
const FORMAT_DATE = 'DD/MM/YYYY';
@@ -106,7 +105,7 @@ export class StartTaskComponent implements OnInit, OnDestroy {
this.taskForm.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((taskFormValues) => this.setTaskDetails(taskFormValues));
}
whitespaceValidator(control: UntypedFormControl): any {
private whitespaceValidator(control: UntypedFormControl): any {
if (control.value) {
const isWhitespace = (control.value || '').trim().length === 0;
const isControlValid = control.value.length === 0 || !isWhitespace;
@@ -136,9 +135,10 @@ export class StartTaskComponent implements OnInit, OnDestroy {
switchMap((createRes) =>
this.attachForm(createRes.id, this.taskDetailsModel.formKey).pipe(
defaultIfEmpty(createRes),
switchMap((attachRes) =>
this.assignTaskByUserId(createRes.id, this.assigneeId).pipe(defaultIfEmpty(attachRes ? attachRes : createRes))
)
switchMap((attachRes) => {
const assigneeId = this.assigneeId ? this.assigneeId.toString() : null;
return this.assignTaskByUserId(createRes.id, assigneeId).pipe(defaultIfEmpty(attachRes ? attachRes : createRes));
})
)
)
)
@@ -154,7 +154,7 @@ export class StartTaskComponent implements OnInit, OnDestroy {
);
}
getAssigneeId(userId: number): void {
setAssigneeId(userId: number): void {
this.assigneeId = userId;
}
@@ -162,10 +162,6 @@ export class StartTaskComponent implements OnInit, OnDestroy {
this.cancel.emit();
}
isUserNameEmpty(user: UserProcessModel): boolean {
return !user || (this.isEmpty(user.firstName) && this.isEmpty(user.lastName));
}
getDisplayUser(firstName: string, lastName: string, delimiter: string = '-'): string {
firstName = firstName !== null ? firstName : '';
lastName = lastName !== null ? lastName : '';
@@ -209,10 +205,6 @@ export class StartTaskComponent implements OnInit, OnDestroy {
return this.taskForm.get('description');
}
get formKeyController(): AbstractControl {
return this.taskForm.get('formKey');
}
private attachForm(taskId: string, formKey: string): Observable<any> {
let response: any = EMPTY;
if (taskId && formKey) {
@@ -221,7 +213,7 @@ export class StartTaskComponent implements OnInit, OnDestroy {
return response;
}
private assignTaskByUserId(taskId: string, userId: any): Observable<TaskDetailsModel> {
private assignTaskByUserId(taskId: string, userId: string): Observable<TaskDetailsModel> {
if (taskId && userId) {
return this.taskService.assignTaskByUserId(taskId, userId);
}
@@ -231,8 +223,4 @@ export class StartTaskComponent implements OnInit, OnDestroy {
private loadFormsTask(): void {
this.forms$ = this.taskService.getFormList();
}
private isEmpty(data: string): boolean {
return data === undefined || data === null || data.trim().length === 0;
}
}

View File

@@ -51,14 +51,12 @@
<adf-info-drawer-tab label="ADF_TASK_LIST.DETAILS.LABELS.INFO_DRAWER_TAB_DETAILS_TITLE">
<div class="adf-assignment-container" *ngIf="showAssignee">
<adf-people-search
[headerTitle]="'ADF_TASK_LIST.DETAILS.LABELS.ADD_ASSIGNEE'"
[actionLabel]="'ADF_TASK_LIST.PEOPLE.ADD_ASSIGNEE'"
(searchPeople)="searchUser($event)"
(success)="assignTaskToUser($event)"
(closeSearch)="onCloseSearch()"
[results]="peopleSearch">
<ng-container adf-people-search-title>{{ 'ADF_TASK_LIST.DETAILS.LABELS.ADD_ASSIGNEE' | translate }}
</ng-container>
<ng-container adf-people-search-action-label>{{ 'ADF_TASK_LIST.PEOPLE.ADD_ASSIGNEE' | translate }}
</ng-container>
</adf-people-search>
</div>
<adf-task-header

View File

@@ -28,18 +28,18 @@ import { ProcessTestingModule } from '../../testing/process.testing.module';
import { TaskService } from '../../form/services/task.service';
import { TaskFormService } from '../../form/services/task-form.service';
import { TaskCommentsService } from '../../task-comments/services/task-comments.service';
import { UserProcessModel } from '../../common/models/user-process.model';
import { PeopleProcessService } from '../../common/services/people-process.service';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HarnessLoader } from '@angular/cdk/testing';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { LightUserRepresentation } from '@alfresco/js-api';
const fakeUser = new UserProcessModel({
id: 'fake-id',
const fakeUser: LightUserRepresentation = {
id: 0,
firstName: 'fake-name',
lastName: 'fake-last',
email: 'fake@mail.com'
});
};
const fakeTaskAssignResponse = new TaskDetailsModel({
id: 'fake-id',
@@ -385,18 +385,20 @@ describe('TaskDetailsComponent', () => {
id: 1,
firstName: 'fake-test-1',
lastName: 'fake-last-1',
email: 'fake-test-1@test.com'
email: 'fake-test-1@test.com',
avatarId: '1'
},
{
id: 2,
firstName: 'fake-test-2',
lastName: 'fake-last-2',
email: 'fake-test-2@test.com'
email: 'fake-test-2@test.com',
avatarId: '2'
}
])
);
let lastValue: UserProcessModel[];
let lastValue: LightUserRepresentation[];
component.peopleSearch.subscribe((users) => (lastValue = users));
component.searchUser('fake-search-word');
@@ -410,7 +412,7 @@ describe('TaskDetailsComponent', () => {
it('should return an empty list for not valid search', () => {
spyOn(peopleProcessService, 'getWorkflowUsers').and.returnValue(of([]));
let lastValue: UserProcessModel[];
let lastValue: LightUserRepresentation[];
component.peopleSearch.subscribe((users) => (lastValue = users));
component.searchUser('fake-search-word');

View File

@@ -39,13 +39,12 @@ import {
} from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Observable, Observer, of, Subject } from 'rxjs';
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service';
import { catchError, share, takeUntil } from 'rxjs/operators';
import { TaskFormComponent } from './task-form/task-form.component';
import { UserProcessModel } from '../../common/models/user-process.model';
import { PeopleProcessService } from '../../common/services/people-process.service';
import { LightUserRepresentation, TaskQueryRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-task-details',
@@ -164,16 +163,16 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
taskDetails: TaskDetailsModel;
taskFormName: string = null;
taskPeople: UserProcessModel[] = [];
taskPeople: LightUserRepresentation[] = [];
noTaskDetailsTemplateComponent: TemplateRef<any>;
showAssignee = false;
showAttachForm = false;
internalReadOnlyForm = false;
errorDialogRef: MatDialogRef<TemplateRef<any>>;
peopleSearch: Observable<UserProcessModel[]>;
peopleSearch: Observable<LightUserRepresentation[]>;
data: any;
private peopleSearchObserver: Observer<UserProcessModel[]>;
private peopleSearchObserver: Observer<LightUserRepresentation[]>;
private onDestroy$ = new Subject<boolean>();
constructor(
@@ -184,7 +183,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
) {}
ngOnInit() {
this.peopleSearch = new Observable<UserProcessModel[]>((observer) => (this.peopleSearchObserver = observer)).pipe(share());
this.peopleSearch = new Observable<LightUserRepresentation[]>((observer) => (this.peopleSearchObserver = observer)).pipe(share());
if (this.taskId) {
this.loadDetails(this.taskId);
@@ -299,7 +298,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
this.showAssignee = false;
}
assignTaskToUser(selectedUser: UserProcessModel) {
assignTaskToUser(selectedUser: LightUserRepresentation) {
this.taskListService.assignTask(this.taskDetails.id, selectedUser).subscribe(() => {
this.assignTask.emit();
});
@@ -363,16 +362,14 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy {
}
if (this.taskDetails?.involvedPeople) {
this.taskDetails.involvedPeople.forEach((user) => {
this.taskPeople.push(new UserProcessModel(user));
});
this.taskPeople.push(...this.taskDetails.involvedPeople);
}
});
}
}
private loadNextTask(processInstanceId: string, processDefinitionId: string): void {
const requestNode = new TaskQueryRequestRepresentationModel({
const requestNode = new TaskQueryRepresentation({
processInstanceId,
processDefinitionId
});

View File

@@ -20,15 +20,35 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { AppConfigService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs';
import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model';
import { TaskListService } from '../services/tasklist.service';
import { TaskFilterService } from '../services/task-filter.service';
import { TaskFiltersComponent } from './task-filters.component';
import { ProcessTestingModule } from '../../testing/process.testing.module';
import { By } from '@angular/platform-browser';
import { fakeTaskFilters } from '../../mock/task/task-filters.mock';
import { NavigationStart, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { UserTaskFilterRepresentation } from '@alfresco/js-api';
const fakeTaskFilters = [
new UserTaskFilterRepresentation({
name: 'FakeInvolvedTasks',
icon: 'glyphicon-align-left',
id: 10,
filter: { state: 'open', assignment: 'fake-involved' }
}),
new UserTaskFilterRepresentation({
name: 'FakeMyTasks1',
icon: 'glyphicon-ok-sign',
id: 11,
filter: { state: 'open', assignment: 'fake-assignee' }
}),
new UserTaskFilterRepresentation({
name: 'FakeMyTasks2',
icon: 'glyphicon-inbox',
id: 12,
filter: { state: 'open', assignment: 'fake-assignee' }
})
];
describe('TaskFiltersComponent', () => {
let taskListService: TaskListService;
@@ -114,7 +134,7 @@ describe('TaskFiltersComponent', () => {
it('should select the task filter based on the input by name param', () => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeTaskFilters));
component.filterParam = new FilterParamsModel({ name: 'FakeMyTasks1' });
component.filterParam = new UserTaskFilterRepresentation({ name: 'FakeMyTasks1' });
let lastValue: any;
component.success.subscribe((res) => (lastValue = res));
@@ -130,7 +150,7 @@ describe('TaskFiltersComponent', () => {
it('should select the task filter based on the input by index param', () => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeTaskFilters));
component.filterParam = new FilterParamsModel({ index: 2 });
component.filterParam = new UserTaskFilterRepresentation({ index: 2 });
let lastValue: any;
component.success.subscribe((res) => (lastValue = res));
@@ -147,7 +167,7 @@ describe('TaskFiltersComponent', () => {
it('should select the task filter based on the input by id param', () => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeTaskFilters));
component.filterParam = new FilterParamsModel({ id: 10 });
component.filterParam = new UserTaskFilterRepresentation({ id: 10 });
const appId = '1';
const change = new SimpleChange(null, appId, true);
@@ -166,7 +186,7 @@ describe('TaskFiltersComponent', () => {
spyOn(component.filterSelected, 'emit');
component.filters = fakeTaskFilters;
const filterParam = new FilterParamsModel({ id: 10 });
const filterParam = new UserTaskFilterRepresentation({ id: 10 });
const change = new SimpleChange(null, filterParam, true);
component.filterParam = filterParam;
@@ -257,7 +277,7 @@ describe('TaskFiltersComponent', () => {
});
it('should not change the current filter if no filter with taskid is found', () => {
const filter = new FilterRepresentationModel({
const filter = new UserTaskFilterRepresentation({
name: 'FakeMyTasks',
filter: { state: 'open', assignment: 'fake-assignee' }
});
@@ -300,7 +320,7 @@ describe('TaskFiltersComponent', () => {
it('should reset selection when filterParam is a filter that does not exist', async () => {
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(of(fakeTaskFilters));
component.currentFilter = fakeTaskFilters[0];
component.filterParam = new FilterRepresentationModel({ name: 'non-existing-filter' });
component.filterParam = { name: 'non-existing-filter' };
const appId = '1';
const change = new SimpleChange(null, appId, true);

View File

@@ -17,13 +17,13 @@
import { AppsProcessService } from '../../app-list/services/apps-process.service';
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model';
import { Subject } from 'rxjs';
import { TaskFilterService } from './../services/task-filter.service';
import { TaskListService } from './../services/tasklist.service';
import { IconModel } from '../../app-list/icon.model';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
import { UserTaskFilterRepresentation } from '@alfresco/js-api';
@Component({
selector: 'adf-task-filters',
@@ -37,15 +37,15 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
* the default filter (the first one the list) is selected.
*/
@Input()
filterParam: FilterParamsModel;
filterParam: UserTaskFilterRepresentation;
/** Emitted when a filter is being clicked from the UI. */
@Output()
filterClicked = new EventEmitter<FilterRepresentationModel>();
filterClicked = new EventEmitter<UserTaskFilterRepresentation>();
/** Emitted when a filter is being selected based on the filterParam input. */
@Output()
filterSelected = new EventEmitter<FilterRepresentationModel>();
filterSelected = new EventEmitter<UserTaskFilterRepresentation>();
/** Emitted when the list is loaded. */
@Output()
@@ -67,11 +67,8 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
@Input()
showIcon: boolean;
filter$: Observable<FilterRepresentationModel>;
currentFilter: FilterRepresentationModel;
filters: FilterRepresentationModel[] = [];
currentFilter: UserTaskFilterRepresentation;
filters: UserTaskFilterRepresentation[] = [];
private onDestroy$ = new Subject<boolean>();
isTaskRoute: boolean;
@@ -117,24 +114,10 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
}
}
isActiveRoute(filterActive: FilterRepresentationModel): boolean {
isActiveRoute(filterActive: UserTaskFilterRepresentation): boolean {
return (this.isTaskActive || this.isTaskRoute) && this.currentFilter === filterActive;
}
/**
* Load the task list filtered by appId or by appName
*
* @param appId application id
* @param appName application name
*/
getFilters(appId?: number, appName?: string): void {
if (appName) {
this.getFiltersByAppName(appName);
} else {
this.getFiltersByAppId(appId);
}
}
/**
* Return the filter list filtered by appId
*
@@ -142,7 +125,7 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
*/
getFiltersByAppId(appId?: number) {
this.taskFilterService.getTaskListFilters(appId).subscribe(
(res: FilterRepresentationModel[]) => {
(res) => {
if (res.length === 0 && this.isFilterListEmpty()) {
this.createFiltersByAppId(appId);
} else {
@@ -179,9 +162,9 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
*
* @param appId application id
*/
createFiltersByAppId(appId?: number): void {
private createFiltersByAppId(appId?: number): void {
this.taskFilterService.createDefaultFilters(appId).subscribe(
(resDefault: FilterRepresentationModel[]) => {
(resDefault) => {
this.resetFilter();
this.filters = resDefault;
this.selectFilter(this.filterParam);
@@ -198,7 +181,7 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
*
* @param newFilter new filter model
*/
public selectFilter(newFilter: FilterParamsModel): void {
public selectFilter(newFilter: UserTaskFilterRepresentation): void {
if (newFilter) {
this.currentFilter = this.filters.find(
(entry, index) =>
@@ -209,7 +192,7 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
}
}
public selectFilterAndEmit(newFilter: FilterParamsModel) {
private selectFilterAndEmit(newFilter: UserTaskFilterRepresentation) {
this.selectFilter(newFilter);
this.filterSelected.emit(this.currentFilter);
}
@@ -219,7 +202,7 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
*
* @param filterParams filter parameters model
*/
onFilterClick(filterParams: FilterParamsModel) {
onFilterClick(filterParams: UserTaskFilterRepresentation) {
this.selectFilter(filterParams);
this.filterClicked.emit(this.currentFilter);
}
@@ -230,9 +213,9 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
* @param taskId task id
*/
selectFilterWithTask(taskId: string): void {
const filteredFilterList: FilterRepresentationModel[] = [];
const filteredFilterList: UserTaskFilterRepresentation[] = [];
this.taskListService.getFilterForTaskById(taskId, this.filters).subscribe(
(filterModel: FilterRepresentationModel) => {
(filterModel) => {
filteredFilterList.push(filterModel);
},
(err) => {
@@ -247,21 +230,12 @@ export class TaskFiltersComponent implements OnInit, OnChanges, OnDestroy {
);
}
/**
* Select as default task filter the first in the list
*/
public selectDefaultTaskFilter(): void {
if (!this.isFilterListEmpty()) {
this.currentFilter = this.filters[0];
}
}
/**
* Get the current filter
*
* @returns filter model
*/
getCurrentFilter(): FilterRepresentationModel {
getCurrentFilter(): UserTaskFilterRepresentation {
return this.currentFilter;
}

View File

@@ -21,7 +21,7 @@ import { TaskDetailsModel } from '../../models/task-details.model';
import { TaskListService } from '../../services/tasklist.service';
import { UserRepresentation } from '@alfresco/js-api';
import { Observable } from 'rxjs';
import { PeopleProcessService } from '../../../common/services/people-process.service';
import { PeopleProcessService } from '../../../common';
@Component({
selector: 'adf-task-form',
@@ -122,7 +122,6 @@ export class TaskFormComponent implements OnInit, OnChanges {
taskDetails: TaskDetailsModel;
currentLoggedUser: UserRepresentation;
loading: boolean = false;
completedTaskMessage: string;
internalReadOnlyForm: boolean = false;
constructor(
@@ -218,10 +217,6 @@ export class TaskFormComponent implements OnInit, OnChanges {
return !this.taskDetails?.processDefinitionId;
}
isTaskLoaded(): boolean {
return !!this.taskDetails;
}
isCompletedTask(): boolean {
return !!this.taskDetails?.endDate;
}
@@ -314,10 +309,6 @@ export class TaskFormComponent implements OnInit, OnChanges {
return this.isCandidateMember() && this.isAssignedToMe() && !this.isCompletedTask();
}
reloadTask() {
this.loadTask(this.taskId);
}
onClaimTask(taskId: string) {
this.taskClaimed.emit(taskId);
}

Some files were not shown because too many files have changed in this diff Show More