From 7d76d789b240756b947c034670e95c8a626c5527 Mon Sep 17 00:00:00 2001 From: Wojciech Duda <69160975+wojd0@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:35:55 +0200 Subject: [PATCH] AAE-41418 Add process instance id input to task header cloud component (#11897) * AAE-41418 Add process instance id input to task header cloud component * AAE-41418 Remove falsy early returns from task header process instance id helpers * AAE-41418 Move process instance id fallback into task header refresh --- .../task-header-cloud.component.spec.ts | 33 +++++++++++++++++-- .../components/task-header-cloud.component.ts | 33 +++++++++++++++++-- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts index 512cc2e0eb..e5bc0ea588 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts @@ -20,7 +20,7 @@ import { of, throwError } from 'rxjs'; import { By } from '@angular/platform-browser'; import { ComponentFixture, TestBed, fakeAsync, flush, discardPeriodicTasks } from '@angular/core/testing'; import { AlfrescoApiService } from '@alfresco/adf-content-services'; -import { AppConfigService, NoopAuthModule } from '@alfresco/adf-core'; +import { AppConfigService, ClipboardService, NoopAuthModule } from '@alfresco/adf-core'; import { TaskCloudService } from '../../services/task-cloud.service'; import { assignedTaskDetailsCloudMock, @@ -106,7 +106,7 @@ describe('TaskHeaderCloudComponent', () => { fixture.detectChanges(); await fixture.whenStable(); expect(getTaskByIdSpy).toHaveBeenCalled(); - expect(component.taskDetails).toBe(assignedTaskDetailsCloudMock); + expect(component.taskDetails).toEqual(assignedTaskDetailsCloudMock); }); it('should display assignee', async () => { @@ -159,6 +159,35 @@ describe('TaskHeaderCloudComponent', () => { expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad'); }); + it('should use processInstanceId input when task details omit it', async () => { + const taskWithoutProcessInstanceId = { ...assignedTaskDetailsCloudMock, processInstanceId: null }; + getTaskByIdSpy.and.returnValue(of(taskWithoutProcessInstanceId)); + component.processInstanceId = 'input-process-instance-id'; + component.ngOnChanges(); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + const valueEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-processInstanceId"]')); + + expect(valueEl.nativeElement.value).toBe('input-process-instance-id'); + expect(component.taskDetails.processInstanceId).toBe('input-process-instance-id'); + }); + + it('should copy process instance id to clipboard when clicked', async () => { + const clipboardService = TestBed.inject(ClipboardService); + const copySpy = spyOn(clipboardService, 'copyContentToClipboard').and.stub(); + + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + + const processInstanceIdToggle = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-toggle-processInstanceId"]')); + processInstanceIdToggle.nativeElement.click(); + + expect(copySpy).toHaveBeenCalledWith('67c4z2a8f-01f3-11e9-8e36-0a58646002ad', 'CORE.METADATA.ACCESSIBILITY.COPY_TO_CLIPBOARD_MESSAGE'); + }); + it('should display placeholder if no due date', async () => { component.taskDetails.dueDate = null; component.refreshData(); diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts index 1f8314760e..3d9082ced5 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts @@ -30,6 +30,8 @@ import { CardViewSelectItemModel, CardViewTextItemModel, CardViewUpdateService, + ClipboardService, + ClickNotification, TranslationService, UpdateNotification } from '@alfresco/adf-core'; @@ -65,6 +67,10 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { @Input() showTitle: boolean = true; + /** Process instance id used when task details do not include it. */ + @Input() + processInstanceId?: string; + /** Emitted when the task is claimed. */ @Output() claim: EventEmitter = new EventEmitter(); @@ -87,9 +93,9 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { dateLocale: string; displayDateClearAction = false; isLoading = true; - processInstanceId: string; private readonly destroyRef = inject(DestroyRef); + private readonly clipboardService = inject(ClipboardService); constructor() { this.dateFormat = this.appConfig.get('adf-cloud-task-header.defaultDateFormat'); @@ -102,6 +108,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { }); this.cardViewUpdateService.itemUpdated$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(this.updateTaskDetails.bind(this)); + this.cardViewUpdateService.itemClicked$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(this.onPropertyClicked.bind(this)); } ngOnChanges() { @@ -132,7 +139,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { this.taskDetails = taskDetails; this.candidateGroups = candidateGroups.map((user) => ({ icon: 'group', value: user }) as CardViewArrayItem); this.candidateUsers = candidateUsers.map((group) => ({ icon: 'person', value: group }) as CardViewArrayItem); - this.processInstanceId = taskDetails.processInstanceId; if (this.taskDetails.parentTaskId) { this.loadParentName(`${this.taskDetails.parentTaskId}`); } else { @@ -217,7 +223,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { }), new CardViewTextItemModel({ label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.PROCESS_INSTANCE_ID', - value: this.processInstanceId, + value: this.taskDetails.processInstanceId, default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.PROCESS_INSTANCE_ID_DEFAULT'), key: 'processInstanceId', clickable: true @@ -256,6 +262,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { */ refreshData() { if (this.taskDetails) { + this.taskDetails = this.applyProcessInstanceIdToTaskDetails(this.taskDetails); const defaultProperties = this.initDefaultProperties(); const filteredProperties: string[] = this.appConfig.get('adf-cloud-task-header.presets.properties'); this.properties = defaultProperties.filter((cardItem) => this.isValidSelection(filteredProperties, cardItem)); @@ -279,10 +286,30 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges { .subscribe((taskDetails) => { if (taskDetails) { this.taskDetails = taskDetails; + this.refreshData(); } }); } + private applyProcessInstanceIdToTaskDetails(taskDetails: TaskDetailsCloudModel): TaskDetailsCloudModel { + const resolvedProcessInstanceId = taskDetails.processInstanceId ?? this.processInstanceId; + + return resolvedProcessInstanceId === taskDetails.processInstanceId + ? taskDetails + : { ...taskDetails, processInstanceId: resolvedProcessInstanceId }; + } + + private onPropertyClicked(clickNotification: ClickNotification): void { + if (clickNotification.target.key === 'processInstanceId') { + this.copyProcessInstanceIdToClipboard(clickNotification.target.value); + } + } + + private copyProcessInstanceIdToClipboard(processInstanceId: string): void { + const clipboardMessage = this.translationService.instant('CORE.METADATA.ACCESSIBILITY.COPY_TO_CLIPBOARD_MESSAGE'); + this.clipboardService.copyContentToClipboard(processInstanceId, clipboardMessage); + } + private loadParentName(taskId: string) { this.taskCloudService.getTaskById(this.appName, taskId).subscribe((taskDetails) => { this.parentTaskName = taskDetails.name;