From d85ad3a397526b4d3753b07044a7c3a1cebaa03f Mon Sep 17 00:00:00 2001 From: "tomek.hanaj" Date: Thu, 9 Jan 2025 14:17:46 +0100 Subject: [PATCH 1/4] [AAE-29973] updated dynamic component inputs and outputs --- .../screen-cloud/screen-cloud.component.ts | 67 ++++++++++++++++--- .../screen-cloud/screen-cloud.interface.ts | 29 ++++++++ .../user-task-cloud.component.html | 5 ++ 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts index 9868e53013..62cd99ea33 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts @@ -16,9 +16,11 @@ */ import { CommonModule } from '@angular/common'; -import { Component, ComponentRef, inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { Component, ComponentRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; import { ScreenRenderingService } from '../../../services/public-api'; import { MatCardModule } from '@angular/material/card'; +import { UserTaskCustomUi } from './screen-cloud.interface'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'adf-cloud-task-screen', @@ -35,29 +37,72 @@ export class TaskScreenCloudComponent implements OnInit { /** Screen id to fetch corresponding screen widget. */ @Input() screenId: string = ''; + @Input() + processInstanceID: string = ''; + @Input() + taskName: string = ''; /** Toggle readonly state of the task. */ @Input() readOnly = false; + /** Emitted when the task is saved. */ + @Output() + taskSaved = new EventEmitter(); + + /** Emitted when the task is completed. */ + @Output() + taskCompleted = new EventEmitter(); + + /** Emitted when there is an error. */ + @Output() + error = new EventEmitter(); + @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef; - componentRef: ComponentRef; + componentRef: ComponentRef; private readonly screenRenderingService = inject(ScreenRenderingService); ngOnInit() { + this.createDynamicComponent(); + } + + createDynamicComponent() { if (this.screenId) { const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId }); this.componentRef = this.container.createComponent(componentType); - if (this.taskId) { - this.componentRef.setInput('taskId', this.taskId); - } - if (this.appName) { - this.componentRef.setInput('appName', this.appName); - } - if (this.screenId) { - this.componentRef.setInput('screenId', this.screenId); - } + this.setInputsForDynamicComponent(); + this.subscribeToOutputs(); + } + } + + setInputsForDynamicComponent(): void { + if (this.taskId) { + this.componentRef.setInput('taskId', this.taskId); + } + if (this.appName) { + this.componentRef.setInput('appName', this.appName); + } + if (this.screenId) { + this.componentRef.setInput('screenId', this.screenId); + } + if (this.processInstanceID) { + this.componentRef.setInput('processInstanceID', this.processInstanceID); + } + if (this.taskName) { + this.componentRef.setInput('taskName', this.taskName); + } + } + + subscribeToOutputs() { + if (this.componentRef.instance?.taskSaved) { + this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed()).subscribe(() => this.taskSaved.emit()); + } + if (this.componentRef.instance?.taskCompleted) { + this.componentRef.instance.taskCompleted.pipe(takeUntilDestroyed()).subscribe(() => this.taskCompleted.emit()); + } + if (this.componentRef.instance?.error) { + this.componentRef.instance.error.pipe(takeUntilDestroyed()).subscribe((data) => this.error.emit(data)); } } } diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts new file mode 100644 index 0000000000..590850ad29 --- /dev/null +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts @@ -0,0 +1,29 @@ +/*! + * @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 { EventEmitter } from '@angular/core'; + +export interface UserTaskCustomUi { + processInstanceID: string; + taskName: string; + appName: string; + taskId: string; + screenId: string; + taskCompleted: EventEmitter; + taskSaved: EventEmitter; + error: EventEmitter; +} diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html index 94b4d1e4ff..dc161db076 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html @@ -29,6 +29,11 @@ [appName]="appName" [screenId]="screenId" [taskId]="taskId" + [taskName]="taskDetails.name" + [processInstanceID]="taskDetails.processInstanceId" + (error)="onError($event)" + (taskCompleted)="onCompleteTask()" + (taskSaved)="onFormSaved()" > From 4a20235fe296aab9ede91832a84e1fc125c5e4b6 Mon Sep 17 00:00:00 2001 From: "tomek.hanaj" Date: Fri, 10 Jan 2025 10:48:42 +0100 Subject: [PATCH 2/4] [AAE-29973] added check to prevent errors --- .../screen-cloud/screen-cloud.component.ts | 14 +++++++------- .../screen-cloud/screen-cloud.interface.ts | 2 +- .../user-task-cloud/user-task-cloud.component.html | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts index 62cd99ea33..f090514db3 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts @@ -38,7 +38,7 @@ export class TaskScreenCloudComponent implements OnInit { @Input() screenId: string = ''; @Input() - processInstanceID: string = ''; + processInstanceId: string = ''; @Input() taskName: string = ''; /** Toggle readonly state of the task. */ @@ -77,19 +77,19 @@ export class TaskScreenCloudComponent implements OnInit { } setInputsForDynamicComponent(): void { - if (this.taskId) { + if (this.taskId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskId')) { this.componentRef.setInput('taskId', this.taskId); } - if (this.appName) { + if (this.appName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'appName')) { this.componentRef.setInput('appName', this.appName); } - if (this.screenId) { + if (this.screenId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'screenId')) { this.componentRef.setInput('screenId', this.screenId); } - if (this.processInstanceID) { - this.componentRef.setInput('processInstanceID', this.processInstanceID); + if (this.processInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'processInstanceId')) { + this.componentRef.setInput('processInstanceId', this.processInstanceId); } - if (this.taskName) { + if (this.taskName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskName')) { this.componentRef.setInput('taskName', this.taskName); } } diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts index 590850ad29..ab095f013d 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.interface.ts @@ -18,7 +18,7 @@ import { EventEmitter } from '@angular/core'; export interface UserTaskCustomUi { - processInstanceID: string; + processInstanceId: string; taskName: string; appName: string; taskId: string; diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html index dc161db076..8d2cc4717e 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html @@ -30,7 +30,7 @@ [screenId]="screenId" [taskId]="taskId" [taskName]="taskDetails.name" - [processInstanceID]="taskDetails.processInstanceId" + [processInstanceId]="taskDetails.processInstanceId" (error)="onError($event)" (taskCompleted)="onCompleteTask()" (taskSaved)="onFormSaved()" From 16852991fbab0cb04a674e56ff5bfb793e9aee76 Mon Sep 17 00:00:00 2001 From: "tomek.hanaj" Date: Fri, 10 Jan 2025 15:23:01 +0100 Subject: [PATCH 3/4] [AAE-29973] added unit tests for input and output --- .../screen-cloud.component.spec.ts | 49 +++++++++++++++---- .../screen-cloud/screen-cloud.component.ts | 10 ++-- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts index 214e5e57c8..f8693b68e7 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts @@ -17,30 +17,46 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ScreenRenderingService } from '../../../services/public-api'; import { TaskScreenCloudComponent } from './screen-cloud.component'; @Component({ selector: 'adf-cloud-test-component', - template: `
test component
`, + template: ` +
+ test component +
{{ taskId }}
+ +
+ `, imports: [CommonModule], standalone: true }) -class TestComponent {} +class TestComponent { + @Input() taskId = ''; + @Output() taskCompleted = new EventEmitter(); + onComplete() { + this.taskCompleted.emit(); + } +} @Component({ selector: 'adf-cloud-test-actions-component', - template: ` -
- -
-
`, + template: ` + +
+ +
+
+ `, imports: [CommonModule, TaskScreenCloudComponent], standalone: true }) -class TestWrapperComponent {} +class TestWrapperComponent { + onTaskCompleted() {} +} describe('TaskScreenCloudComponent', () => { let fixture: ComponentFixture; @@ -66,4 +82,19 @@ describe('TaskScreenCloudComponent', () => { const projectedContent = fixture.debugElement.query(By.css('.adf-cloud-test-buttons')); expect(projectedContent).toBeTruthy(); }); + + it('should set input property for dynamic component', () => { + const inputValueFromDynamicComponent = fixture.debugElement.query(By.css('.adf-cloud-test-container-taskId')); + expect((inputValueFromDynamicComponent.nativeElement as HTMLElement).textContent).toBe('1'); + }); + + it('should subscribe to the output of dynamic component', () => { + const onTaskCompletedSpy = spyOn(fixture.componentInstance, 'onTaskCompleted'); + const btnComplete = fixture.debugElement.query(By.css('.adf-cloud-test-container-complete-btn')); + + expect(btnComplete).toBeDefined(); + + (btnComplete.nativeElement as HTMLButtonElement).click(); + expect(onTaskCompletedSpy).toHaveBeenCalled(); + }); }); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts index f090514db3..dbe540a9a4 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts @@ -16,7 +16,7 @@ */ import { CommonModule } from '@angular/common'; -import { Component, ComponentRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; +import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; import { ScreenRenderingService } from '../../../services/public-api'; import { MatCardModule } from '@angular/material/card'; import { UserTaskCustomUi } from './screen-cloud.interface'; @@ -59,6 +59,8 @@ export class TaskScreenCloudComponent implements OnInit { @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef; + + private destroyRef = inject(DestroyRef); componentRef: ComponentRef; private readonly screenRenderingService = inject(ScreenRenderingService); @@ -96,13 +98,13 @@ export class TaskScreenCloudComponent implements OnInit { subscribeToOutputs() { if (this.componentRef.instance?.taskSaved) { - this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed()).subscribe(() => this.taskSaved.emit()); + this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit()); } if (this.componentRef.instance?.taskCompleted) { - this.componentRef.instance.taskCompleted.pipe(takeUntilDestroyed()).subscribe(() => this.taskCompleted.emit()); + this.componentRef.instance.taskCompleted.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskCompleted.emit()); } if (this.componentRef.instance?.error) { - this.componentRef.instance.error.pipe(takeUntilDestroyed()).subscribe((data) => this.error.emit(data)); + this.componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data)); } } } From ad2cec1d79abbc0c5d8d5343646366a3f91d813b Mon Sep 17 00:00:00 2001 From: "tomek.hanaj" Date: Mon, 13 Jan 2025 08:33:19 +0100 Subject: [PATCH 4/4] [AAE-29973] fix for dynamic component with absolute position --- .../screen/components/screen-cloud/screen-cloud.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html index acd222e7a9..9da96ce9ae 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html @@ -1,5 +1,5 @@ - +