Repository: Alfresco/alfresco-ng2-components
Pull request #10790: [MNT-24950] Fix process file preview button state
Author: login: nikita-web-ua
Labels: name: A/N BDU
Created: 2025-04-14T07:44:06Z | Merged: 2025-04-15T08:05:55Z

**Please check if the PR fulfills these requirements**

> - [X] The commit message follows our [guidelines](https://github.com/Alfresco/alfresco-ng2-components/wiki/Commit-format)
> - [X] Tests for the changes have been added (for bug fixes / features)
> - [ ] Docs have been added / updated (for bug fixes / features)

<!--
 Before submitting your PR, please check that your code follows our contribution guidelines:
 https://github.com/Alfresco/alfresco-ng2-components/wiki/Code-contribution-acceptance-criteria
 -->

**What kind of change does this PR introduce?** (check one with "x")

> - [X] Bugfix
> - [ ] Feature
> - [ ] Code style update (formatting, local variables)
> - [ ] Refactoring (no functional changes, no api changes)
> - [ ] Build related changes
> - [ ] Documentation
> - [ ] Other... Please describe:


**What is the current behaviour?** (You can also link to an open issue here)

https://hyland.atlassian.net/browse/MNT-24950
Preview button state is not correct in some cases

**What is the new behaviour?**

File preview button state is fixed

**Does this PR introduce a breaking change?** (check one with "x")

> - [ ] Yes
> - [X] No


If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...

**Other information**:



Diff:
diff --git a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.html b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.html
index 701577cb16c..f9e01ac2914 100644
--- a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.html
+++ b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.html
@@ -83,7 +83,7 @@
             <mat-menu #fileActionMenu="matMenu" xPosition="before">
                 <button
                     id="{{ 'file-' + file.id + '-show-file' }}"
-                    [disabled]="file.isExternal || !file.contentAvailable || !file.mimeType"
+                    [disabled]="file.isExternal || !file.mimeType || (!file.sourceId && (isStartProcessPage || !file.contentAvailable))"
                     mat-menu-item
                     (click)="onAttachFileClicked(file)"
                 >
diff --git a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts
index ee35a2ab444..5f72fe13eb5 100644
--- a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts
+++ b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts
@@ -26,7 +26,8 @@ import {
     FormFieldMetadata,
     DownloadService,
     AppConfigService,
-    AppConfigValues
+    AppConfigValues,
+    UnitTestingUtils
 } from '@alfresco/adf-core';
 import { ContentNodeDialogService } from '@alfresco/adf-content-services';
 import { of } from 'rxjs';
@@ -37,6 +38,9 @@ import { ActivitiContentService } from '../../services/activiti-alfresco.service
 import { ProcessContentService } from '../../services/process-content.service';
 import { RouterTestingModule } from '@angular/router/testing';
 import { ActivatedRoute, Router } from '@angular/router';
+import { HarnessLoader } from '@angular/cdk/testing';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { MatMenuHarness } from '@angular/material/menu/testing';
 
 const fakeRepositoryListAnswer = [
     {
@@ -145,6 +149,8 @@ const fakePngAnswer: any = {
 describe('AttachFileWidgetComponent', () => {
     let widget: AttachFileWidgetComponent;
     let fixture: ComponentFixture<AttachFileWidgetComponent>;
+    let loader: HarnessLoader;
+    let unitTestingUtils: UnitTestingUtils;
     let element: HTMLInputElement;
     let activitiContentService: ActivitiContentService;
     let router: Router;
@@ -173,6 +179,8 @@ describe('AttachFileWidgetComponent', () => {
         });
         fixture = TestBed.createComponent(AttachFileWidgetComponent);
         widget = fixture.componentInstance;
+        loader = TestbedHarnessEnvironment.loader(fixture);
+        unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
         element = fixture.nativeElement;
         router = TestBed.inject(Router);
         activatedRoute = TestBed.inject(ActivatedRoute);
@@ -189,6 +197,16 @@ describe('AttachFileWidgetComponent', () => {
         fixture.destroy();
     });
 
+    const isPreviewButtonDisabled = async (fileId: string): Promise<boolean> => {
+        const menu = await loader.getHarness(MatMenuHarness);
+        unitTestingUtils.clickByCSS(`#file-${fileId}-option-menu`);
+        fixture.detectChanges();
+        await fixture.whenStable();
+        const items = await menu.getItems();
+        const showFileButton = items[0];
+        return showFileButton.isDisabled();
+    };
+
     it('should add file to tempFilesList when form has value and file source is configured', () => {
         spyOn(activitiContentService, 'getAlfrescoRepositories').and.returnValue(of(fakeRepositoryListAnswer));
         spyOn(widget, 'isFileSourceConfigured').and.returnValue(true);
@@ -608,6 +626,115 @@ describe('AttachFileWidgetComponent', () => {
             const showOption = fixture.debugElement.query(By.css('#file-1155-show-file')).nativeElement as HTMLButtonElement;
             expect(showOption.disabled).toBeTruthy();
         });
+
+        it('should set isStartProcessPage to true when URL contains start-process', () => {
+            spyOnProperty(router, 'url', 'get').and.returnValue('/start-process?foo=bar');
+            widget.ngOnInit();
+            expect(widget.isStartProcessPage).toBeTrue();
+        });
+
+        it('should set isStartProcessPage to false when URL does not contain start-process', () => {
+            spyOnProperty(router, 'url', 'get').and.returnValue('/other-page?foo=start-process');
+            widget.ngOnInit();
+            expect(widget.isStartProcessPage).toBeFalse();
+        });
+
+        it('should disable preview button when file is external', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: true,
+                mimeType: 'application/pdf',
+                contentAvailable: true
+            };
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+
+            expect(await isPreviewButtonDisabled('123')).toBeTrue();
+        });
+
+        it('should disable preview button when file has no mime type', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: false,
+                mimeType: null,
+                contentAvailable: true
+            };
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+
+            expect(await isPreviewButtonDisabled('123')).toBeTrue();
+        });
+
+        it('should disable preview button when content is not available and is start process page', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: false,
+                mimeType: 'application/pdf',
+                contentAvailable: false,
+                sourceId: null
+            };
+            widget.isStartProcessPage = true;
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+
+            expect(await isPreviewButtonDisabled('123')).toBeTrue();
+        });
+
+        it('should enable preview button when all conditions are met', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: false,
+                mimeType: 'application/pdf',
+                contentAvailable: true,
+                sourceId: '456'
+            };
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+
+            expect(await isPreviewButtonDisabled('123')).toBeFalse();
+        });
+
+        it('should enable preview button and ignore start process page check when content is available', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: false,
+                mimeType: 'application/pdf',
+                contentAvailable: true,
+                sourceId: '1234'
+            };
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+            widget.isStartProcessPage = true;
+            fixture.detectChanges();
+
+            expect(await isPreviewButtonDisabled('123')).toBeFalse();
+        });
+
+        it('should enable preview button when sourceId exists even if content is not available', async () => {
+            const testFile = {
+                id: '123',
+                isExternal: false,
+                mimeType: 'application/pdf',
+                contentAvailable: false,
+                sourceId: '456'
+            };
+            widget.field = new FormFieldModel(new FormModel(), {
+                type: FormFieldTypes.UPLOAD,
+                value: [testFile]
+            });
+
+            expect(await isPreviewButtonDisabled('123')).toBeFalse();
+        });
     });
 
     it('should be able to upload files when a defined folder from external content service', async () => {
diff --git a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts
index 3b27ba20250..08c6c91b824 100644
--- a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts
+++ b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts
@@ -18,21 +18,9 @@
 /* eslint-disable @angular-eslint/component-selector */
 
 import { Component, DestroyRef, inject, isDevMode, OnInit, ViewEncapsulation } from '@angular/core';
-import {
-    AppConfigService,
-    AppConfigValues,
-    DownloadService,
-    ErrorWidgetComponent,
-    FormService,
-    ThumbnailService
-} from '@alfresco/adf-core';
+import { AppConfigService, AppConfigValues, DownloadService, ErrorWidgetComponent, FormService, ThumbnailService } from '@alfresco/adf-core';
 import { AlfrescoIconComponent, ContentNodeDialogService, ContentService } from '@alfresco/adf-content-services';
-import {
-    AlfrescoEndpointRepresentation,
-    Node,
-    NodeChildAssociation,
-    RelatedContentRepresentation
-} from '@alfresco/js-api';
+import { AlfrescoEndpointRepresentation, Node, NodeChildAssociation, RelatedContentRepresentation } from '@alfresco/js-api';
 import { from, of, zip } from 'rxjs';
 import { mergeMap } from 'rxjs/operators';
 import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service';
@@ -79,6 +67,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 export class AttachFileWidgetComponent extends UploadWidgetComponent implements OnInit {
     typeId = 'AttachFileWidgetComponent';
     repositoryList: AlfrescoEndpointRepresentation[] = [];
+    isStartProcessPage = false;
+
     private tempFilesList = [];
 
     private readonly destroyRef = inject(DestroyRef);
@@ -101,7 +91,7 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements
 
     ngOnInit() {
         super.ngOnInit();
-
+        this.isStartProcessPage = this.router.url.split('?')[0].includes('start-process');
         if (Array.isArray(this.field.value) && this.isFileSourceConfigured()) {
             this.tempFilesList.push(...this.field.value);
         }
@@ -185,7 +175,7 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements
     }
 
     onAttachFileClicked(file: any) {
-        if (file.isExternal || !file.contentAvailable) {
+        if (file.isExternal || (!file.sourceId && (this.isStartProcessPage || !file.contentAvailable))) {
             return;
         }
         if (this.isTemporaryFile(file)) {
@@ -292,16 +282,12 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements
             );
     }
 
-
     private updateNodesParams(): void {
-        this.router.navigate(
-            [],
-            {
-                relativeTo: this.activatedRoute,
-                queryParams: { nodes: this.tempFilesList.map(file => file.id).join(',') },
-                queryParamsHandling: 'merge'
-            }
-        );
+        this.router.navigate([], {
+            relativeTo: this.activatedRoute,
+            queryParams: { nodes: this.tempFilesList.map((file) => file.id).join(',') },
+            queryParamsHandling: 'merge'
+        });
     }
 
     private getDomainHost(urlToCheck: string): string {


Reviews:
user: login: AleksanderSklorz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T09:44:38Z
user: login: AleksanderSklorz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T09:50:09Z
user: login: nikita-web-ua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T09:59:19Z
user: login: AleksanderSklorz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T10:02:02Z

Review comments:
diff_hunk: @@ -189,6 +194,17 @@ describe('AttachFileWidgetComponent', () => {
         fixture.destroy();
     });
 
+    const isPreviewButtonDisabled = async (fileId: string): Promise<boolean> => {
+        const menu = await loader.getHarness(MatMenuHarness);
+        await fixture.debugElement.query(By.css(`#file-${fileId}-option-menu`)).nativeElement.click();
path: lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts
user: login: AleksanderSklorz
body: instead of using fixture.debugElement directly please use UnitTestingUtils class (there is clickByCSS function). 
created_at: 2025-04-14T09:44:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,6 +67,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 export class AttachFileWidgetComponent extends UploadWidgetComponent implements OnInit {
     typeId = 'AttachFileWidgetComponent';
     repositoryList: AlfrescoEndpointRepresentation[] = [];
+    isStartProcessPage = false;
path: lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts
user: login: AleksanderSklorz
body: can we add space between private and non-private fields? 
created_at: 2025-04-14T09:50:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -189,6 +194,17 @@ describe('AttachFileWidgetComponent', () => {
         fixture.destroy();
     });
 
+    const isPreviewButtonDisabled = async (fileId: string): Promise<boolean> => {
+        const menu = await loader.getHarness(MatMenuHarness);
+        await fixture.debugElement.query(By.css(`#file-${fileId}-option-menu`)).nativeElement.click();
path: lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.spec.ts
user: login: nikita-web-ua
body: done
created_at: 2025-04-14T09:59:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-14T12:53:25Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=Alfresco_alfresco-ng2-components&pullRequest=10790) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=Alfresco_alfresco-ng2-components&pullRequest=10790&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=Alfresco_alfresco-ng2-components&pullRequest=10790&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Alfresco_alfresco-ng2-components&pullRequest=10790&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=Alfresco_alfresco-ng2-components&pullRequest=10790&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=Alfresco_alfresco-ng2-components&pullRequest=10790&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Alfresco_alfresco-ng2-components&pullRequest=10790)


author_association: NONE