From c3755d266ed24f3eec91d3482caad84c15957f5d Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 22 Apr 2026 14:52:02 +0200 Subject: [PATCH 1/7] [NAE-2416] AbstractFileDefaultFieldComponent does not push upload event notification to TaskEventService - Integrated `FrontActionService` in `AbstractFileDefaultFieldComponent` to handle front-end actions based on outcomes. - Enhanced file upload logic with better error handling and front action triggers, such as displaying success feedback via snackbar actions. - Updated tests for `AbstractFileListDefaultFieldComponent` to include `FrontActionService`. - Registered a new `snackBar` front action in the `FrontActionModule`. - Refactored the file upload methods to utilize modern RXJS subscription patterns with error handling improvements. - Improved readability and maintainability of file download and preview logic by addressing structuring and formatting issues. - Removed unused imports and simplified test components. These changes aim to improve the user experience by handling outcome-based front actions and offering better feedback on file operations. --- .../src/lib/actions/front-action.module.ts | 3 +- ...tract-file-default-field.component.spec.ts | 8 +- .../abstract-file-default-field.component.ts | 105 ++++++++++-------- ...-file-list-default-field.component.spec.ts | 8 +- ...tract-file-list-default-field.component.ts | 100 +++++++++-------- .../file-default-field.component.ts | 5 +- .../file-list-default-field.component.ts | 5 +- 7 files changed, 128 insertions(+), 106 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/actions/front-action.module.ts b/projects/netgrif-components-core/src/lib/actions/front-action.module.ts index 926acea17..c45918912 100644 --- a/projects/netgrif-components-core/src/lib/actions/front-action.module.ts +++ b/projects/netgrif-components-core/src/lib/actions/front-action.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import {FrontActionRegistryService} from "../registry/front-action-registry.service"; -import {redirectAction} from "./model/router-action-definitions"; +import {redirectAction, snackBarAction} from "./model/router-action-definitions"; import {reloadTaskAction, validateTaskAction} from "./model/task-action-definitions"; @NgModule({ @@ -16,5 +16,6 @@ export class FrontActionModule { frontActionsRegistry.register('redirect', redirectAction); frontActionsRegistry.register('validate', validateTaskAction); frontActionsRegistry.register('reloadTask', reloadTaskAction); + frontActionsRegistry.register('snackBar', snackBarAction); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index e66f177b5..ee15de46e 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -16,9 +16,6 @@ import {MockUserResourceService} from "../../../utility/tests/mocks/mock-user-re import {ConfigurationService} from "../../../configuration/configuration.service"; import {TestConfigurationService} from "../../../utility/tests/test-config"; import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, Optional} from "@angular/core"; -import {BrowserDynamicTestingModule} from "@angular/platform-browser-dynamic/testing"; -import {ErrorSnackBarComponent} from "../../../snack-bar/components/error-snack-bar/error-snack-bar.component"; -import {SuccessSnackBarComponent} from "../../../snack-bar/components/success-snack-bar/success-snack-bar.component"; import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service"; import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; @@ -29,6 +26,7 @@ import {AbstractFileDefaultFieldComponent} from "./abstract-file-default-field.c import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token"; import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; +import {FrontActionService} from "../../../actions/services/front-action.service"; describe('AbstractFileDefaultFieldComponent', () => { let component: TestFileComponent; @@ -48,6 +46,7 @@ describe('AbstractFileDefaultFieldComponent', () => { providers: [ SideMenuService, EventService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, @@ -97,8 +96,9 @@ class TestFileComponent extends AbstractFileDefaultFieldComponent { translate: TranslateService, sanitizer: DomSanitizer, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, sanitizer, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, sanitizer, frontActionService, dataFieldPortalData); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts index 690a84ab7..75342fcba 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts @@ -26,6 +26,8 @@ import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-fie import {FILE_FIELD_HEIGHT, FILE_FIELD_PADDING, PREVIEW, PREVIEW_BUTTON} from '../models/file-field-constants'; import {FileFieldRequest} from "../../../resources/interface/file-field-request-body"; import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component'; +import {FrontAction} from "../../models/changed-fields"; +import {FrontActionService} from "../../../actions/services/front-action.service"; export interface FileState { progress: number; @@ -114,6 +116,7 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel protected _translate: TranslateService, protected _eventService: EventService, protected _sanitizer: DomSanitizer, + protected _frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { super(_log, _snackbar, _translate, dataFieldPortalData); this.state = this.defaultState; @@ -217,60 +220,68 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel fileFormData.append('file', fileToUpload); fileFormData.append('data', new Blob([JSON.stringify(this.createRequestBody())], {type: 'application/json'})); this._taskResourceService.uploadFile(this.taskId, fileFormData, false) - .subscribe((response: EventOutcomeMessageResource) => { - if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { - this.state.progress = (response as ProviderProgress).progress; - } else { - this.state.completed = true; - this.state.uploading = false; - this.state.progress = 0; + .subscribe({ + next: (response: EventOutcomeMessageResource) => { + if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { + this.state.progress = (response as ProviderProgress).progress; + } else { + this.state.completed = true; + this.state.uploading = false; + this.state.progress = 0; - if (response.error) { - this.state.error = true; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error - ); if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + this.state.error = true; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + ); + if (response.error) { + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + } } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); + this.dataField.emitChangedFields(changedFieldsMap); + this._log.debug( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` + ); + this.state.error = false; + this.dataField.downloaded = false; + this.dataField.value.name = fileToUpload.name; + if (this.isFilePreview) { + this.initializePreviewIfDisplayable(); + } + this.fullSource.next(undefined); + this.fileForDownload = undefined; + this.formControlRef.setValue(this.dataField.value.name); + this._snackbar.openSuccessSnackBar(!!response.outcome.message ? response.outcome.message : this._translate.instant('tasks.snackbar.dataSaved')); + const frontActions: Array = this._eventService.parseFrontActionsFromOutcomeTree(response.outcome); + if (frontActions?.length > 0) { + this._frontActionService.runAll(frontActions); + } } + this.dataField.touch = true; + this.dataField.update(); + this.fileUploadEl.nativeElement.value = ''; + } + }, + error: (error) => { + this.state.completed = true; + this.state.error = true; + this.state.uploading = false; + this.state.progress = 0; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + ); + if (error?.error?.message) { + this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); } else { - const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); - this.dataField.emitChangedFields(changedFieldsMap); - this._log.debug( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` - ); - this.state.error = false; - this.dataField.downloaded = false; - this.dataField.value.name = fileToUpload.name; - if (this.isFilePreview) { - this.initializePreviewIfDisplayable(); - } - this.fullSource.next(undefined); - this.fileForDownload = undefined; - this.formControlRef.setValue(this.dataField.value.name); + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } this.dataField.touch = true; this.dataField.update(); this.fileUploadEl.nativeElement.value = ''; } - }, error => { - this.state.completed = true; - this.state.error = true; - this.state.uploading = false; - this.state.progress = 0; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error - ); - if (error?.error?.message) { - this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } - this.dataField.touch = true; - this.dataField.update(); - this.fileUploadEl.nativeElement.value = ''; }); } @@ -410,7 +421,8 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel this.state.downloading = true; let params = new HttpParams() params = params.set("fieldId", this.dataField.stringId); - this._taskResourceService.downloadFilePreview(this.resolveParentTaskId(), params).subscribe(response => { if (response instanceof Blob) { + this._taskResourceService.downloadFilePreview(this.resolveParentTaskId(), params).subscribe(response => { + if (response instanceof Blob) { this._log.debug(`Preview of file [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`); this.fileForPreview = new Blob([response], {type: 'application/octet-stream'}); this.previewSource = this._sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(this.fileForPreview)); @@ -445,7 +457,8 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel } let params = new HttpParams(); params = params.set("fieldId", this.dataField.stringId); - this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => { if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) { + this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => { + if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) { this._log.debug(`File [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`); this.initDownloadFile(response); } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index e03a36ea6..2d9bb5574 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -16,9 +16,6 @@ import {MockUserResourceService} from "../../../utility/tests/mocks/mock-user-re import {ConfigurationService} from "../../../configuration/configuration.service"; import {TestConfigurationService} from "../../../utility/tests/test-config"; import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, Optional} from "@angular/core"; -import {BrowserDynamicTestingModule} from "@angular/platform-browser-dynamic/testing"; -import {ErrorSnackBarComponent} from "../../../snack-bar/components/error-snack-bar/error-snack-bar.component"; -import {SuccessSnackBarComponent} from "../../../snack-bar/components/success-snack-bar/success-snack-bar.component"; import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service"; import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; @@ -28,6 +25,7 @@ import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-fie import {AbstractFileListDefaultFieldComponent} from "./abstract-file-list-default-field.component"; import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; +import {FrontActionService} from "../../../actions/services/front-action.service"; describe('AbstractFileListDefaultFieldComponent', () => { let component: TestFileListComponent; @@ -46,6 +44,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { providers: [ SideMenuService, EventService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, @@ -94,8 +93,9 @@ class TestFileListComponent extends AbstractFileListDefaultFieldComponent { snackbar: SnackBarService, translate: TranslateService, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, frontActionService, dataFieldPortalData); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 9fb99c8ee..7513b72e7 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -1,13 +1,10 @@ import { AfterViewInit, Component, - ElementRef, Inject, - Input, OnDestroy, OnInit, Optional, - ViewChild } from "@angular/core"; import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token"; import {FileListField, FileListFieldValidation} from "../models/file-list-field"; @@ -17,16 +14,16 @@ import {LoggerService} from "../../../logger/services/logger.service"; import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; import {TranslateService} from "@ngx-translate/core"; import {EventService} from "../../../event/services/event.service"; -import {FileFieldIdBody} from "../../models/file-field-id-body"; import {EventOutcomeMessageResource} from "../../../resources/interface/message-resource"; import {ProgressType, ProviderProgress} from "../../../resources/resource-provider.service"; import {ChangedFieldsMap} from "../../../event/services/interfaces/changed-fields-map"; import {HttpParams} from "@angular/common/http"; import {take} from "rxjs/operators"; import {FileFieldValue} from "../../file-field/models/file-field-value"; -import {AbstractBaseDataFieldComponent} from "../../base-component/abstract-base-data-field.component"; import {FileFieldRequest} from "../../../resources/interface/file-field-request-body"; import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component'; +import {FrontAction} from "../../models/changed-fields"; +import {FrontActionService} from "../../../actions/services/front-action.service"; export interface FilesState { progress: number; @@ -60,6 +57,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile protected _snackbar: SnackBarService, protected _translate: TranslateService, protected _eventService: EventService, + protected _frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { super(_log, _snackbar, _translate, dataFieldPortalData); this.state = this.defaultState; @@ -171,58 +169,66 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile fieldId: this.dataField.stringId, } fileFormData.append('data', new Blob([JSON.stringify(requestBody)], {type: 'application/json'})); - this._taskResourceService.uploadFile(this.taskId, fileFormData, true).subscribe((response: EventOutcomeMessageResource) => { - if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { - this.state.progress = (response as ProviderProgress).progress; - } else { - this.state.completed = true; - this.state.uploading = false; - this.state.progress = 0; - this._log.debug( - `Files [${this.dataField.stringId}] were successfully uploaded` - ); - if (response.error) { - this.state.error = true; - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + this._taskResourceService.uploadFile(this.taskId, fileFormData, true) + .subscribe({ + next: (response: EventOutcomeMessageResource) => { + if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) { + this.state.progress = (response as ProviderProgress).progress; + } else { + this.state.completed = true; + this.state.uploading = false; + this.state.progress = 0; + this._log.debug( + `Files [${this.dataField.stringId}] were successfully uploaded` ); if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + this.state.error = true; + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + ); + if (response.error) { + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + } } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); + const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); + this.dataField.emitChangedFields(changedFieldsMap); + this.state.error = false; + filesToUpload.forEach(fileToUpload => { + this.uploadedFiles.push(fileToUpload.name); + this.dataField.value.namesPaths.push({name: fileToUpload.name}); + this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => { + return namePath['name']; + }).join('/')); + }); + this._snackbar.openSuccessSnackBar(!!response.outcome.message ? response.outcome.message : this._translate.instant('tasks.snackbar.dataSaved')); + const frontActions: Array = this._eventService.parseFrontActionsFromOutcomeTree(response.outcome); + if (frontActions?.length > 0) { + this._frontActionService.runAll(frontActions); + } } + this.dataField.touch = true; + this.dataField.update(); + this.fileUploadEl.nativeElement.value = ''; + } + }, error: (error) => { + this.state.completed = true; + this.state.error = true; + this.state.uploading = false; + this.state.progress = 0; + if (error?.error?.message) { + this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); } else { - const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); - this.dataField.emitChangedFields(changedFieldsMap); - this.state.error = false; - filesToUpload.forEach(fileToUpload => { - this.uploadedFiles.push(fileToUpload.name); - this.dataField.value.namesPaths.push({name: fileToUpload.name}); - this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => { - return namePath['name']; - }).join('/')); - }); + this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } + this._log.error( + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + ); this.dataField.touch = true; this.dataField.update(); this.fileUploadEl.nativeElement.value = ''; } - }, error => { - this.state.completed = true; - this.state.error = true; - this.state.uploading = false; - this.state.progress = 0; - if (error?.error?.message) { - this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } - this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error - ); - this.dataField.touch = true; - this.dataField.update(); - this.fileUploadEl.nativeElement.value = ''; }); } diff --git a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts index 378008fd9..f770cc156 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.ts @@ -5,7 +5,7 @@ import { LoggerService, SnackBarService, TaskResourceService, - AbstractFileDefaultFieldComponent + AbstractFileDefaultFieldComponent, FrontActionService } from "@netgrif/components-core"; import {TranslateService} from "@ngx-translate/core"; import {DomSanitizer} from "@angular/platform-browser"; @@ -26,8 +26,9 @@ export class FileDefaultFieldComponent extends AbstractFileDefaultFieldComponent eventService: EventService, protected _sanitizer: DomSanitizer, protected dialog: MatDialog, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, _sanitizer, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, _sanitizer, frontActionService, dataFieldPortalData); } public showPreviewDialog() { diff --git a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts index 9a4d00a7f..e581ce13b 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.ts @@ -7,7 +7,7 @@ import { EventService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - FileListField, AbstractFileListDefaultFieldComponent + FileListField, AbstractFileListDefaultFieldComponent, FrontActionService } from '@netgrif/components-core' @Component({ @@ -22,7 +22,8 @@ export class FileListDefaultFieldComponent extends AbstractFileListDefaultFieldC snackbar: SnackBarService, translate: TranslateService, eventService: EventService, + frontActionService: FrontActionService, @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData) { - super(taskResourceService, log, snackbar, translate, eventService, dataFieldPortalData); + super(taskResourceService, log, snackbar, translate, eventService, frontActionService, dataFieldPortalData); } } From 4d83a8140d6d91dc85aacaf8021c0aa456414732 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 27 Apr 2026 13:55:57 +0200 Subject: [PATCH 2/7] Add FrontActionService to test providers in file components Included FrontActionService in the test setup for file-list-default-field and file-default-field components. This ensures proper dependency injection and facilitates testing of features relying on FrontActionService. --- .../file-default-field/file-default-field.component.spec.ts | 3 ++- .../file-list-default-field.component.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts index 4c4365875..1a8b3abc7 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-field/file-default-field/file-default-field.component.spec.ts @@ -6,7 +6,7 @@ import { AuthenticationService, ConfigurationService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - ErrorSnackBarComponent, FileField, + ErrorSnackBarComponent, FileField, FrontActionService, MaterialModule, MockAuthenticationMethodService, MockAuthenticationService, @@ -39,6 +39,7 @@ describe('FileDefaultFieldComponent', () => { ], providers: [ SideMenuService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, diff --git a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts index 02b15542d..062c1a3e3 100644 --- a/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts +++ b/projects/netgrif-components/src/lib/data-fields/file-list-field/file-list-default-field/file-list-default-field.component.spec.ts @@ -7,7 +7,7 @@ import { ConfigurationService, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, - FileListField, + FileListField, FrontActionService, MaterialModule, MockAuthenticationMethodService, MockAuthenticationService, @@ -42,6 +42,7 @@ describe('FileListDefaultFieldComponent', () => { ], providers: [ SideMenuService, + FrontActionService, {provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService}, {provide: AuthenticationService, useClass: MockAuthenticationService}, {provide: UserResourceService, useClass: MockUserResourceService}, From 1a5e592559b923bdbcaa5991ccf7169fe2f59639 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 08:57:12 +0200 Subject: [PATCH 3/7] Fix logging and error handling in file upload components Reorganized logging to ensure success messages are logged after error checks and refined error handling logic for file uploads. Added a null-safe access to file names and fixed missing return statement in front-action error handling. --- .../lib/actions/services/front-action.service.ts | 1 + .../abstract-file-default-field.component.ts | 13 +++++-------- .../abstract-file-list-default-field.component.ts | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts b/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts index e245d1dd8..8a4c0bbc7 100644 --- a/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts +++ b/projects/netgrif-components-core/src/lib/actions/services/front-action.service.ts @@ -16,6 +16,7 @@ export class FrontActionService { const fn = this._frontActionRegistry.get(frontAction.id) if (!fn) { this._log.error("Frontend action is not defined for ID [" + frontAction.id +"]") + return; } fn.call(this._injector, frontAction) } diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts index 75342fcba..726cbc928 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts @@ -232,18 +232,15 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel if (response.error) { this.state.error = true; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, response.error ); - if (response.error) { - this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); - } + this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); + } else { const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); this.dataField.emitChangedFields(changedFieldsMap); this._log.debug( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded` + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} was successfully uploaded` ); this.state.error = false; this.dataField.downloaded = false; @@ -271,7 +268,7 @@ export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFiel this.state.uploading = false; this.state.progress = 0; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, error ); if (error?.error?.message) { this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message)); diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index 7513b72e7..b75e27a9b 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -178,20 +178,18 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile this.state.completed = true; this.state.uploading = false; this.state.progress = 0; - this._log.debug( - `Files [${this.dataField.stringId}] were successfully uploaded` - ); if (response.error) { this.state.error = true; this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} uploading has failed!`, response.error ); if (response.error) { this._snackbar.openErrorSnackBar(this._translate.instant(response.error)); - } else { - this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } } else { + this._log.debug( + `Files [${this.dataField.stringId}] were successfully uploaded` + ); const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome); this.dataField.emitChangedFields(changedFieldsMap); this.state.error = false; From 7594f6a0519ae57c0633ea337acc05c133429f6e Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:12:48 +0200 Subject: [PATCH 4/7] Update file handling and event parsing, add test cases Refined file upload error logging and improved event parsing logic to handle frontActions. Added unit tests to verify file upload and download methods, ensuring better component reliability. Updated test dependencies to support new functionality. --- ...act-file-list-default-field.component.spec.ts | 16 ++++++++++++++-- ...abstract-file-list-default-field.component.ts | 2 +- .../src/lib/event/services/event.service.ts | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index 2d9bb5574..b295ad888 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -1,8 +1,8 @@ -import {ComponentFixture, TestBed, waitForAsync} from "@angular/core/testing"; +import {ComponentFixture, inject, TestBed, waitForAsync} from "@angular/core/testing"; import {MaterialModule} from "../../../material/material.module"; import {AngularResizeEventModule} from "angular-resize-event"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import {HttpClientTestingModule} from "@angular/common/http/testing"; +import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing"; import {TranslateLibModule} from "../../../translate/translate-lib.module"; import {SnackBarModule} from "../../../snack-bar/snack-bar.module"; import {SideMenuService} from "../../../side-menu/services/side-menu.service"; @@ -78,6 +78,18 @@ describe('AbstractFileListDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); + it('should call download method successfully', () => { + spyOn(component, 'download').and.callThrough(); // Spy on the method + component.download("test"); // Call the method + expect(component.download).toHaveBeenCalled(); // Assert that it was called + }); + + it('should call upload method successfully', () => { + spyOn(component, 'upload').and.callThrough(); // Spy on the method + component.upload(); // Call the method + expect(component.upload).toHaveBeenCalled(); // Assert that it was called + }); + afterEach(() => { TestBed.resetTestingModule(); }); diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts index b75e27a9b..80d573ead 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts @@ -221,7 +221,7 @@ export abstract class AbstractFileListDefaultFieldComponent extends AbstractFile this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed')); } this._log.error( - `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error + `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)?.name} uploading has failed!`, error ); this.dataField.touch = true; this.dataField.update(); diff --git a/projects/netgrif-components-core/src/lib/event/services/event.service.ts b/projects/netgrif-components-core/src/lib/event/services/event.service.ts index b8805bbfd..cec572d1f 100644 --- a/projects/netgrif-components-core/src/lib/event/services/event.service.ts +++ b/projects/netgrif-components-core/src/lib/event/services/event.service.ts @@ -57,6 +57,9 @@ export class EventService { public parseFrontActionsFromOutcomeTree(outcome: EventOutcome): Array { const frontActions: Array = []; + if (!!outcome.frontActions) { + frontActions.push(...outcome.frontActions); + } if (!!outcome.outcomes && outcome.outcomes.length > 0) { return this.parseFrontActionsFromOutcomeTreeRecursive(outcome.outcomes, frontActions); } else return frontActions; From 340d2594b4cdb28b466704885e65e1c767ebd145 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:42:06 +0200 Subject: [PATCH 5/7] Update file list field test with additional imports and template Added missing imports for AbstractFileListFieldComponent and invalid data token to the test file. Updated the test component's template to include a file input element with binding to allowed file types. --- .../abstract-file-list-default-field.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts index b295ad888..093cafd8e 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.spec.ts @@ -26,6 +26,8 @@ import {AbstractFileListDefaultFieldComponent} from "./abstract-file-list-defaul import {FormControl} from "@angular/forms"; import {WrappedBoolean} from "../../data-field-template/models/wrapped-boolean"; import {FrontActionService} from "../../../actions/services/front-action.service"; +import {AbstractFileListFieldComponent} from "../abstract-file-list-field.component"; +import {NAE_INFORM_ABOUT_INVALID_DATA} from "../../models/invalid-data-policy-token"; describe('AbstractFileListDefaultFieldComponent', () => { let component: TestFileListComponent; @@ -97,7 +99,7 @@ describe('AbstractFileListDefaultFieldComponent', () => { @Component({ selector: 'ncc-test-filelist', - template: '' + template: '' }) class TestFileListComponent extends AbstractFileListDefaultFieldComponent { constructor(taskResourceService: TaskResourceService, From b8599953a1a4c524363a6e1d16c975e9912732fd Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 13:42:43 +0200 Subject: [PATCH 6/7] Add unit tests for download and upload methods Implemented tests to verify that the download and upload methods are called successfully. These tests improve the coverage and ensure the methods function as expected. --- .../abstract-file-default-field.component.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index ee15de46e..3862af930 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -80,6 +80,18 @@ describe('AbstractFileDefaultFieldComponent', () => { expect(component).toBeTruthy(); }); + it('should call download method successfully', () => { + spyOn(component, 'download').and.callThrough(); // Spy on the method + component.download(); // Call the method + expect(component.download).toHaveBeenCalled(); // Assert that it was called + }); + + it('should call upload method successfully', () => { + spyOn(component, 'upload').and.callThrough(); // Spy on the method + component.upload(); // Call the method + expect(component.upload).toHaveBeenCalled(); // Assert that it was called + }); + afterEach(() => { TestBed.resetTestingModule(); }); From 26db13640f205bc4c8d8b2108f6a864d74bcb609 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 28 Apr 2026 14:00:26 +0200 Subject: [PATCH 7/7] Add unit tests for download and upload methods Implemented tests to verify that the download and upload methods are called successfully. These tests improve the coverage and ensure the methods function as expected. --- .../abstract-file-default-field.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts index 3862af930..625e55093 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.spec.ts @@ -99,7 +99,7 @@ describe('AbstractFileDefaultFieldComponent', () => { @Component({ selector: 'ncc-test-file', - template: '' + template: '' }) class TestFileComponent extends AbstractFileDefaultFieldComponent { constructor(taskResourceService: TaskResourceService,