From c1b508299d39fac4df42d26365f5325200f41588 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 7 Apr 2026 15:46:02 +0300 Subject: [PATCH] Add IoT Hub integration for calculated fields, subtype support, error handling - IotHubApiService injected in dialog constructor, removed from data interface - Add optional entityId to dialog data, passed to installItemVersion for CF - Add optional itemSubType to dialog data and fixedSubType to browse component - fixedSubType: applies subtype filter, hides subtype filter section/chips, excluded from active filters and clear all, hides card subtype badge - Add "Add from IoT Hub" to calculated fields table with entityId - Rule chains: pass CORE/EDGE subType based on ruleChainScope, fix navigation - Show alert dialog on install failure with error details - Remove IotHubApiService from all dialog data callers --- .../calculated-fields-table-config.ts | 29 +++++++++++++++++++ .../iot-hub-add-item-dialog.component.html | 1 + .../iot-hub-add-item-dialog.component.ts | 27 ++++++++++++++--- .../iot-hub/iot-hub-browse.component.html | 8 ++--- .../iot-hub/iot-hub-browse.component.ts | 17 +++++++++-- .../dashboards-table-config.resolver.ts | 5 +--- .../rulechains-table-config.resolver.ts | 13 ++++++--- 7 files changed, 82 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts index 72805454ff..d8e53c2b85 100644 --- a/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts @@ -57,6 +57,12 @@ import { ImportExportService } from '@shared/import-export/import-export.service import { isObject } from '@core/utils'; import { EntityDebugSettingsService } from '@home/components/entity/debug/entity-debug-settings.service'; import { DatePipe } from '@angular/common'; +import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; +import { + TbIotHubAddItemDialogComponent, + IotHubAddItemDialogData, + IotHubAddItemDialogResult +} from '@home/components/iot-hub/iot-hub-add-item-dialog.component'; export class CalculatedFieldsTableConfig extends EntityTableConfig { @@ -104,6 +110,12 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig true, onAction: () => this.importCalculatedField() + }, + { + name: this.translate.instant('iot-hub.add-from-iot-hub'), + icon: 'store', + isEnabled: () => true, + onAction: () => this.addCalculatedFieldFromIotHub() } ]; @@ -243,6 +255,23 @@ export class CalculatedFieldsTableConfig extends EntityTableConfig { + if (result?.descriptor) { + this.updateData(); + } + }); + } + private importCalculatedField(): void { this.importExportService.openCalculatedFieldImportDialog() .pipe( diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.html index 2d1514a5d6..ac3b50d4bc 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-add-item-dialog.component.html @@ -33,6 +33,7 @@ } , - private translate: TranslateService + private translate: TranslateService, + private iotHubApiService: IotHubApiService, + private dialogService: DialogService ) { this.itemType = data.itemType; + this.itemSubType = data.itemSubType; } getTitle(): string { @@ -59,15 +66,27 @@ export class TbIotHubAddItemDialogComponent { onAddItem(item: MpItemVersionView): void { this.isInstalling = true; const versionId = item.id as string; - this.data.iotHubApiService.installItemVersion(versionId, { ignoreLoading: true }).subscribe({ + const installData = this.data.entityId ? { entityId: this.data.entityId } : undefined; + this.iotHubApiService.installItemVersion(versionId, { ignoreLoading: true }, installData).subscribe({ next: (result) => { this.isInstalling = false; if (result.success) { this.dialogRef.close({ item, descriptor: result.descriptor } as IotHubAddItemDialogResult); + } else { + const message = result.errorMessage || this.translate.instant('iot-hub.install-error', { name: item.name }); + this.dialogService.alert( + this.translate.instant('iot-hub.install-error-title'), + message + ); } }, - error: () => { + error: (err) => { this.isInstalling = false; + const message = err?.error?.message || err?.message || this.translate.instant('iot-hub.install-error', { name: item.name }); + this.dialogService.alert( + this.translate.instant('iot-hub.install-error-title'), + message + ); } }); } diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html index eab3d4d501..596db43d84 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html @@ -57,7 +57,7 @@
- @if (getSubtypeMap(); as subtypeMap) { + @if (!fixedSubType && getSubtypeMap(); as subtypeMap) { {{ 'iot-hub.type-filter-label' | translate }} @@ -72,7 +72,7 @@ } @if (categories.size > 0) { - @if (getSubtypeMap()) { + @if (!fixedSubType && getSubtypeMap()) {
} @@ -88,7 +88,7 @@ } - @if (getSubtypeMap() || categories.size > 0) { + @if ((!fixedSubType && getSubtypeMap()) || categories.size > 0) {
} @@ -208,7 +208,7 @@ [item]="item" [installedItem]="getInstalledItem(item)" [showTypeChip]="false" - [showSubtype]="true" + [showSubtype]="!fixedSubType" [showCreator]="!creatorId" [mode]="mode" (cardClick)="openItemDetail($event)" diff --git a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts index ffac043baf..b30ad84564 100644 --- a/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts +++ b/ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts @@ -67,6 +67,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { @Input() embedded = false; @Input() hideTabs = false; @Input() mode: 'default' | 'add' = 'default'; + @Input() fixedSubType: string; @Output() addItem = new EventEmitter(); @Input() set activeType(value: ItemType) { if (value && value !== this._activeType) { @@ -136,6 +137,12 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { if (params['search']) { this.textSearch = params['search']; } + if (this.fixedSubType) { + const subtypes = this.getActiveSubtypes(); + if (subtypes) { + subtypes.add(this.fixedSubType); + } + } this.updateCategories(); if (this.activeType === ItemType.WIDGET) { this.loadInstalledWidgets(); @@ -304,6 +311,9 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { } getActiveSubtypesArray(): string[] { + if (this.fixedSubType) { + return Array.from(this.getActiveSubtypes()).filter(s => s !== this.fixedSubType); + } return Array.from(this.getActiveSubtypes()); } @@ -359,6 +369,9 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { this.activeCfTypes.clear(); this.activeWidgetTypes.clear(); this.activeRuleChainTypes.clear(); + if (this.fixedSubType) { + this.getActiveSubtypes()?.add(this.fixedSubType); + } this.textSearch = ''; this.updateCategories(); this.pageIndex = 0; @@ -366,9 +379,9 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy { } hasActiveDropdownFilters(): boolean { + const subtypeCount = this.fixedSubType ? this.getActiveSubtypesArray().length : (this.getActiveSubtypes()?.size || 0); return this.activeCategories.size > 0 || - this.activeUseCases.size > 0 || this.activeCfTypes.size > 0 || - this.activeWidgetTypes.size > 0 || this.activeRuleChainTypes.size > 0; + this.activeUseCases.size > 0 || subtypeCount > 0; } hasActiveFilters(): boolean { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index 84476cbf11..ce2e947d03 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -78,7 +78,6 @@ import { EntityAliasesDialogData } from '@home/components/alias/entity-aliases-dialog.component'; import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; -import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { TbIotHubAddItemDialogComponent, IotHubAddItemDialogData, @@ -97,7 +96,6 @@ export class DashboardsTableConfigResolver { private dialogService: DialogService, private homeDialogs: HomeDialogsService, private importExport: ImportExportService, - private iotHubApiService: IotHubApiService, private translate: TranslateService, private datePipe: DatePipe, private router: Router, @@ -408,8 +406,7 @@ export class DashboardsTableConfigResolver { disableClose: true, autoFocus: false, data: { - itemType: ItemType.DASHBOARD, - iotHubApiService: this.iotHubApiService + itemType: ItemType.DASHBOARD } as IotHubAddItemDialogData }); dialogRef.afterClosed().subscribe((result: IotHubAddItemDialogResult) => { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index b2f9c5ab84..7a4a69da0b 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -50,7 +50,6 @@ import { PageLink } from '@shared/models/page/page-link'; import { Edge } from '@shared/models/edge.models'; import { mergeMap } from 'rxjs/operators'; import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; -import { IotHubApiService } from '@core/http/iot-hub-api.service'; import { TbIotHubAddItemDialogComponent, IotHubAddItemDialogData, @@ -70,7 +69,6 @@ export class RuleChainsTableConfigResolver { private importExport: ImportExportService, private itembuffer: ItemBufferService, private edgeService: EdgeService, - private iotHubApiService: IotHubApiService, private translate: TranslateService, private datePipe: DatePipe, private router: Router, @@ -303,18 +301,25 @@ export class RuleChainsTableConfigResolver { } addRuleChainFromIotHub() { + const ruleChainScope = this.config.componentsData.ruleChainScope; + const ruleChainType = ruleChainScope === 'edges' ? 'EDGE' : 'CORE'; const dialogRef = this.dialog.open(TbIotHubAddItemDialogComponent, { panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], disableClose: true, autoFocus: false, data: { itemType: ItemType.RULE_CHAIN, - iotHubApiService: this.iotHubApiService + itemSubType: ruleChainType } as IotHubAddItemDialogData }); dialogRef.afterClosed().subscribe((result: IotHubAddItemDialogResult) => { if (result?.descriptor?.type === 'RULE_CHAIN' && result.descriptor.ruleChainId?.id) { - this.router.navigateByUrl(`ruleChains/${result.descriptor.ruleChainId.id}`); + const ruleChainId = result.descriptor.ruleChainId.id; + if (ruleChainScope === 'edges') { + this.router.navigateByUrl(`edgeManagement/ruleChains/${ruleChainId}`); + } else { + this.router.navigateByUrl(`ruleChains/${ruleChainId}`); + } } }); }