From 8e1d1afd52cc2e85bd092976b87dac39ba8fb2ef Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Mon, 22 Jun 2026 16:34:27 +0200 Subject: [PATCH 1/4] Fixed alarm rule validity not updating when arguments change (#15820) * Fixed alarm rule validity not updating when arguments change * Code formating fix --- .../alarm-rules/cf-alarm-rule-condition.component.ts | 7 +++---- .../components/alarm-rules/cf-alarm-rule.component.ts | 10 ++++++++++ .../alarm-rules/create-cf-alarm-rules.component.ts | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts index fe0670b4e7..ef539af6f5 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition.component.ts @@ -142,10 +142,9 @@ export class CfAlarmRuleConditionComponent implements ControlValueAccessor, Vali } ngOnChanges(changes: SimpleChanges) { - if (changes.arguments) { - if (changes.arguments && !changes.arguments.firstChange) { - this.recalculateArgumentValidity(); - } + if (changes.arguments && !changes.arguments.firstChange) { + this.recalculateArgumentValidity(); + this.onValidatorChange(); } } diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.ts index 706477f72a..d9b7f868ac 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule.component.ts @@ -84,6 +84,7 @@ export class CfAlarmRuleComponent implements ControlValueAccessor, OnInit, Valid }); private propagateChange = (v: any) => { }; + private onValidatorChange = () => { }; constructor(private dialog: MatDialog, private fb: FormBuilder, @@ -97,12 +98,21 @@ export class CfAlarmRuleComponent implements ControlValueAccessor, OnInit, Valid registerOnTouched(fn: any): void { } + registerOnValidatorChange(fn: () => void): void { + this.onValidatorChange = fn; + } + ngOnInit() { this.alarmRuleFormGroup.valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(() => { this.updateModel(); }); + this.alarmRuleFormGroup.statusChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.onValidatorChange(); + }); } setDisabledState(isDisabled: boolean): void { diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts index 4d9884f851..345e155ae1 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/create-cf-alarm-rules.component.ts @@ -77,12 +77,16 @@ export class CreateCfAlarmRulesComponent implements ControlValueAccessor, Valida private usedSeverities: AlarmSeverity[] = []; private propagateChange = (v: any) => { }; + private onValidatorChange = () => { }; constructor(private fb: FormBuilder, private destroyRef: DestroyRef) { this.createAlarmRulesFormGroup.valueChanges.pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(() => this.updateModel()); + this.createAlarmRulesFormGroup.statusChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => this.onValidatorChange()); } registerOnChange(fn: any): void { @@ -92,6 +96,10 @@ export class CreateCfAlarmRulesComponent implements ControlValueAccessor, Valida registerOnTouched(fn: any): void { } + registerOnValidatorChange(fn: () => void): void { + this.onValidatorChange = fn; + } + createAlarmRulesFormArray(): UntypedFormArray { return this.createAlarmRulesFormGroup.get('createAlarmRules') as UntypedFormArray; } From 7db21f413eb729ff7c6598a8212109ca0d5112fb Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Mon, 22 Jun 2026 16:35:36 +0200 Subject: [PATCH 2/4] Fixed time unit input validation and unwanted dirty state when loading a value (#15822) * Fixed time unit input validation and unwanted dirty state when loading a value * Code improvements --- .../components/time-unit-input.component.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time-unit-input.component.ts b/ui-ngx/src/app/shared/components/time-unit-input.component.ts index 93293698b3..79482f378f 100644 --- a/ui-ngx/src/app/shared/components/time-unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/time-unit-input.component.ts @@ -206,15 +206,13 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, writeValue(sec: number) { if (sec !== this.modelValue) { if (isDefinedAndNotNull(sec) && isNumeric(sec) && Number(sec) !== 0) { - this.timeInputForm.patchValue(this.parseTime(sec), {emitEvent: true}); + this.timeInputForm.patchValue(this.parseTime(sec), {emitEvent: false}); this.modelValue = sec; } else { - this.timeInputForm.patchValue({ - time: 0, - timeUnit: TimeUnit.SECONDS - }, {emitEvent: false}); + this.timeInputForm.patchValue(this.secondsModel(0), {emitEvent: false}); this.modelValue = 0; } + this.refreshTimeValidators(); } } @@ -242,6 +240,14 @@ export class TimeUnitInputComponent implements ControlValueAccessor, Validator, } } } + return this.secondsModel(value); + } + + private secondsModel(time: number): TimeUnitInputModel { + return { + time, + timeUnit: TimeUnit.SECONDS + }; } private createStepMultipleOfValidator(): ValidatorFn { From 61ce61bf5c4504755593784a606ef2a8cc2b1092 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 23 Jun 2026 11:37:26 +0300 Subject: [PATCH 3/4] refactor(iot-hub): move installed-item delete API call into the delete dialog Push the deleteInstalledItem call from IotHubActionsService into the dialog's confirm() handler so the dialog itself manages its lifetime. Pass installedItemId in the dialog data and have confirm() fire the API call, then close with true on success. Drop the now-unused isLoading flips in the installed-items table since loading state is no longer optimistically toggled around the dialog. --- .../components/iot-hub/iot-hub-actions.service.ts | 10 ++-------- .../iot-hub/iot-hub-delete-dialog.component.ts | 12 ++++++++++-- .../iot-hub-installed-items-table.component.ts | 6 ------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts index 702eb6b1d9..d26565ee95 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts @@ -17,7 +17,6 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Observable, of, EMPTY } from 'rxjs'; -import { map, mergeMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { DialogService } from '@core/services/dialog.service'; import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models'; @@ -120,13 +119,8 @@ export class IotHubActionsService { panelClass: ['tb-dialog'], disableClose: true, autoFocus: false, - data: { itemName: installedItem.itemName, itemType: installedItem.itemType } - }).afterClosed().pipe( - mergeMap((confirmed) => - confirmed - ? this.iotHubApiService.deleteInstalledItem(installedItem.id.id).pipe(map(() => true)) - : of(false) ) - ); + data: { installedItemId: installedItem.id.id, itemName: installedItem.itemName, itemType: installedItem.itemType } + }).afterClosed(); } installDevice(item: MpItemVersionView): Observable { diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-delete-dialog.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-delete-dialog.component.ts index 4353f1a9f6..be91644741 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-delete-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-delete-dialog.component.ts @@ -20,8 +20,11 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { DialogComponent } from '@shared/components/dialog.component'; +import { IotHubInstalledItem } from 'src/app/shared/models/iot-hub/iot-hub-installed-item.models'; +import { IotHubApiService } from '@core/http/iot-hub-api.service'; export interface IotHubDeleteDialogData { + installedItemId: string; itemName: string; itemType?: string; } @@ -38,13 +41,18 @@ export class TbIotHubDeleteDialogComponent extends DialogComponent, protected router: Router, protected dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: IotHubDeleteDialogData + @Inject(MAT_DIALOG_DATA) public data: IotHubDeleteDialogData, + private iotHubApiService: IotHubApiService ) { super(store, router, dialogRef); } confirm(): void { - this.dialogRef.close(true); + this.iotHubApiService.deleteInstalledItem(this.data.installedItemId).subscribe( + () => { + this.dialogRef.close(true); + } + ); } cancel(): void { diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts index 281d1d1e0e..4435c82736 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts @@ -156,12 +156,9 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges, } deleteItem(item: IotHubInstalledItem): void { - this.isLoading = true; this.iotHubActions.deleteItem(item).subscribe(deleted => { if (deleted) { this.loadData(); - } else { - this.isLoading = false; } }); } @@ -247,12 +244,9 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges, } updateItem(item: IotHubInstalledItem, publishedInfo: ItemPublishedVersionInfo): void { - this.isLoading = true; this.iotHubActions.updateItem(item, publishedInfo.publishedVersion, publishedInfo.publishedVersionId).subscribe(result => { if (result === 'updated') { this.loadData(); - } else { - this.isLoading = false; } }); } From 48e70f9b0ddd83b7e34dca930ba6609cee65f061 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 23 Jun 2026 11:41:24 +0300 Subject: [PATCH 4/4] chore(iot-hub): drop unused IotHubApiService dependency from IotHubActionsService --- .../modules/home/components/iot-hub/iot-hub-actions.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts index d26565ee95..73e627fbe6 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts @@ -22,7 +22,6 @@ import { DialogService } from '@core/services/dialog.service'; import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models'; import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; import { DeviceInstalledItemDescriptor, IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; -import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { EntityId } from '@shared/models/id/entity-id'; import { TbIotHubAddItemDialogComponent, IotHubAddItemDialogData, IotHubAddItemDialogResult } from './iot-hub-add-item-dialog.component'; import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData, IotHubItemDetailDialogMode } from './iot-hub-item-detail-dialog.component'; @@ -37,7 +36,6 @@ export class IotHubActionsService { constructor( private dialog: MatDialog, - private iotHubApiService: IotHubApiService, private dialogService: DialogService, private translate: TranslateService ) {}