Browse Source

Introduce IotHubActionsService to eliminate duplicated dialog code

- New IotHubActionsService with openItemDetail, installItem, updateItem,
  deleteItem methods returning Observables for dialog results
- Service handles all dialog opening, device install flow (fetch zip + dialog),
  and delete confirmation + API call
- Registered as provider in IotHubComponentsModule
- Refactored browse component: removed all dialog imports, uses service
- Refactored home component: removed dialog imports, uses service
- Refactored search component: removed dialog imports, uses service
- Removed ~150 lines of duplicated dialog code across 3 components
pull/15508/head
Igor Kulikov 6 months ago
parent
commit
a5a805c03a
  1. 103
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts
  2. 86
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts
  3. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-components.module.ts
  4. 94
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
  5. 77
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-search.component.ts

103
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts

@ -0,0 +1,103 @@
///
/// Copyright © 2016-2026 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Observable, of, EMPTY } from 'rxjs';
import { filter, mergeMap } from 'rxjs/operators';
import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models';
import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models';
import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models';
import { IotHubApiService } from '@core/http/iot-hub-api.service';
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData, IotHubItemDetailDialogMode } from './iot-hub-item-detail-dialog.component';
import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from './iot-hub-install-dialog.component';
import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from './iot-hub-update-dialog.component';
import { TbIotHubDeleteDialogComponent, IotHubDeleteDialogData } from './iot-hub-delete-dialog.component';
import { TbDeviceInstallDialogComponent, DeviceInstallDialogData } from './device-install-dialog/device-install-dialog.component';
@Injectable()
export class IotHubActionsService {
constructor(
private dialog: MatDialog,
private iotHubApiService: IotHubApiService
) {}
openItemDetail(item: MpItemVersionView, installedItem?: IotHubInstalledItem, mode?: IotHubItemDetailDialogMode): Observable<any> {
return this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: { item, installedItem, mode } as IotHubItemDetailDialogData
}).afterClosed();
}
installItem(item: MpItemVersionView): Observable<string> {
if (item.type === ItemType.DEVICE) {
return this.installDevice(item);
}
return this.dialog.open(TbIotHubInstallDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { item } as IotHubInstallDialogData
}).afterClosed();
}
updateItem(item: MpItemVersionView, installedItem: IotHubInstalledItem): Observable<string> {
if (!installedItem) {
return EMPTY;
}
return this.dialog.open(TbIotHubUpdateDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
installedItemId: installedItem.id.id,
itemName: item.name,
itemType: item.type,
version: item.version,
versionId: item.id
} as IotHubUpdateDialogData
}).afterClosed();
}
deleteItem(item: MpItemVersionView, installedItem: IotHubInstalledItem): Observable<boolean> {
if (!installedItem) {
return of(false);
}
return this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
}).afterClosed().pipe(
filter(confirmed => !!confirmed),
mergeMap(() => this.iotHubApiService.deleteInstalledItem(installedItem.id.id)),
mergeMap(() => of(true))
);
}
private installDevice(item: MpItemVersionView): Observable<string> {
return this.iotHubApiService.getVersionFileData(item.id as string, { ignoreLoading: true }).pipe(
mergeMap(async (blob: Blob) => {
const zipData = await blob.arrayBuffer();
return this.dialog.open(TbDeviceInstallDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
disableClose: true,
autoFocus: false,
data: { item, zipData } as DeviceInstallDialogData
}).afterClosed().toPromise();
})
);
}
}

86
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

@ -28,14 +28,9 @@ import {
import { cfTypeTranslations, widgetTypeTranslations, ruleChainTypeTranslations } from '@shared/models/iot-hub/iot-hub-version.models';
import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models';
import { IotHubApiService } from '@core/http/iot-hub-api.service';
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from '@home/components/iot-hub/iot-hub-item-detail-dialog.component';
import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from '@home/components/iot-hub/iot-hub-install-dialog.component';
import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from '@home/components/iot-hub/iot-hub-update-dialog.component';
import { TbIotHubDeleteDialogComponent, IotHubDeleteDialogData } from '@home/components/iot-hub/iot-hub-delete-dialog.component';
import { TbDeviceInstallDialogComponent, DeviceInstallDialogData } from '@home/components/iot-hub/device-install-dialog/device-install-dialog.component';
import { IotHubActionsService } from '@home/components/iot-hub/iot-hub-actions.service';
interface SortOption {
value: string;
@ -119,7 +114,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
constructor(
private iotHubApiService: IotHubApiService,
private dialog: MatDialog,
private iotHubActions: IotHubActionsService,
private translate: TranslateService,
private router: Router,
private route: ActivatedRoute
@ -432,16 +427,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
}
openItemDetail(item: MpItemVersionView): void {
const dialogRef = this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: {
item,
installedItem: this.getInstalledItem(item),
mode: this.mode
} as IotHubItemDetailDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.openItemDetail(item, this.getInstalledItem(item), this.mode).subscribe(result => {
if (result?.action === 'add') {
this.addItem.emit(result.item);
} else if (result === 'installed' || result === 'updated' || result === 'deleted') {
@ -455,60 +441,17 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
}
installItem(item: MpItemVersionView): void {
if (item.type === ItemType.DEVICE) {
this.installDevice(item);
return;
}
const dialogRef = this.dialog.open(TbIotHubInstallDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
item,
} as IotHubInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.installItem(item).subscribe(result => {
if (result === 'installed') {
this.reloadInstalledItems();
}
});
}
private installDevice(item: MpItemVersionView): void {
this.iotHubApiService.getVersionFileData(item.id as string, { ignoreLoading: true }).subscribe({
next: async (blob: Blob) => {
const zipData = await blob.arrayBuffer();
const dialogRef = this.dialog.open(TbDeviceInstallDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
disableClose: true,
autoFocus: false,
data: { item, zipData } as DeviceInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
if (result === 'installed') {
this.loadItems();
}
});
}
});
}
updateItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item);
if (!installedItem) {
return;
}
const dialogRef = this.dialog.open(TbIotHubUpdateDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
installedItemId: installedItem.id.id,
itemName: item.name,
itemType: item.type,
version: item.version,
versionId: item.id,
} as IotHubUpdateDialogData
});
dialogRef.afterClosed().subscribe(result => {
if (!installedItem) { return; }
this.iotHubActions.updateItem(item, installedItem).subscribe(result => {
if (result === 'updated') {
this.reloadInstalledItems();
}
@ -517,20 +460,9 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item);
if (!installedItem) {
return;
}
const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
});
dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) {
this.iotHubApiService.deleteInstalledItem(installedItem.id.id).subscribe(() => {
this.reloadInstalledItems();
});
}
if (!installedItem) { return; }
this.iotHubActions.deleteItem(item, installedItem).subscribe(() => {
this.reloadInstalledItems();
});
}

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-components.module.ts

@ -25,6 +25,7 @@ import { TbIotHubInstallDialogComponent } from './iot-hub-install-dialog.compone
import { TbIotHubUpdateDialogComponent } from './iot-hub-update-dialog.component';
import { TbIotHubDeleteDialogComponent } from './iot-hub-delete-dialog.component';
import { TbDeviceInstallDialogComponent } from './device-install-dialog/device-install-dialog.component';
import { IotHubActionsService } from './iot-hub-actions.service';
@NgModule({
declarations: [
@ -41,6 +42,9 @@ import { TbDeviceInstallDialogComponent } from './device-install-dialog/device-i
CommonModule,
SharedModule
],
providers: [
IotHubActionsService
],
exports: [
TbIotHubItemDetailDialogComponent,
TbIotHubBrowseComponent,

94
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -16,7 +16,6 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { BreakpointObserver } from '@angular/cdk/layout';
@ -30,11 +29,7 @@ import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-i
import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models';
import { IotHubApiService } from '@core/http/iot-hub-api.service';
import { TranslateService } from '@ngx-translate/core';
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from '@home/components/iot-hub/iot-hub-item-detail-dialog.component';
import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from '@home/components/iot-hub/iot-hub-install-dialog.component';
import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from '@home/components/iot-hub/iot-hub-update-dialog.component';
import { TbIotHubDeleteDialogComponent, IotHubDeleteDialogData } from '@home/components/iot-hub/iot-hub-delete-dialog.component';
import { TbDeviceInstallDialogComponent, DeviceInstallDialogData } from '@home/components/iot-hub/device-install-dialog/device-install-dialog.component';
import { IotHubActionsService } from '@home/components/iot-hub/iot-hub-actions.service';
interface CategoryCard {
type: ItemType;
@ -142,8 +137,8 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private dialog: MatDialog,
private iotHubApiService: IotHubApiService,
private iotHubActions: IotHubActionsService,
private translate: TranslateService,
private breakpointObserver: BreakpointObserver
) {}
@ -313,16 +308,7 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
}
openItemDetail(item: MpItemVersionView): void {
const installedItem = this.findInstalledItem(item);
const dialogRef = this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: {
item,
installedItem
} as IotHubItemDetailDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.openItemDetail(item, this.findInstalledItem(item)).subscribe(result => {
if (result === 'installed' || result === 'deleted') {
this.reloadInstalledItems(item.type);
}
@ -358,60 +344,17 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
}
installItem(item: MpItemVersionView): void {
if (item.type === ItemType.DEVICE) {
this.installDevice(item);
return;
}
const dialogRef = this.dialog.open(TbIotHubInstallDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
item,
} as IotHubInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.installItem(item).subscribe(result => {
if (result === 'installed') {
this.reloadInstalledItems(item.type);
}
});
}
private installDevice(item: MpItemVersionView): void {
this.iotHubApiService.getVersionFileData(item.id as string, { ignoreLoading: true }).subscribe({
next: async (blob: Blob) => {
const zipData = await blob.arrayBuffer();
const dialogRef = this.dialog.open(TbDeviceInstallDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
disableClose: true,
autoFocus: false,
data: { item, zipData } as DeviceInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
if (result === 'installed') {
this.reloadInstalledItems(item.type);
}
});
}
});
}
updateItem(item: MpItemVersionView): void {
const installedItem = this.findInstalledItem(item);
if (!installedItem) {
return;
}
const dialogRef = this.dialog.open(TbIotHubUpdateDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
installedItemId: installedItem.id.id,
itemName: item.name,
itemType: item.type,
version: item.version,
versionId: item.id,
} as IotHubUpdateDialogData
});
dialogRef.afterClosed().subscribe(result => {
if (!installedItem) { return; }
this.iotHubActions.updateItem(item, installedItem).subscribe(result => {
if (result === 'updated') {
this.reloadInstalledItems(item.type);
}
@ -432,24 +375,13 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.findInstalledItem(item);
if (!installedItem) {
return;
}
const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
});
dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) {
this.iotHubApiService.deleteInstalledItem(installedItem.id.id).subscribe(() => {
this.installedItemsCount = Math.max(0, this.installedItemsCount - 1);
if (item.type === ItemType.WIDGET) {
this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id);
} else if (item.type === ItemType.SOLUTION_TEMPLATE) {
this.installedSolutionTemplates = this.installedSolutionTemplates.filter(i => i.id.id !== installedItem.id.id);
}
});
if (!installedItem) { return; }
this.iotHubActions.deleteItem(item, installedItem).subscribe(() => {
this.installedItemsCount = Math.max(0, this.installedItemsCount - 1);
if (item.type === ItemType.WIDGET) {
this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id);
} else if (item.type === ItemType.SOLUTION_TEMPLATE) {
this.installedSolutionTemplates = this.installedSolutionTemplates.filter(i => i.id.id !== installedItem.id.id);
}
});
}

77
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-search.component.ts

@ -16,7 +16,6 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core';
import { forkJoin, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@ -26,11 +25,7 @@ import { MpItemVersionQuery, MpItemVersionView } from '@shared/models/iot-hub/io
import { ItemType, itemTypeTranslations } from '@shared/models/iot-hub/iot-hub-item.models';
import { IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models';
import { IotHubApiService } from '@core/http/iot-hub-api.service';
import { TbIotHubItemDetailDialogComponent, IotHubItemDetailDialogData } from '@home/components/iot-hub/iot-hub-item-detail-dialog.component';
import { TbIotHubInstallDialogComponent, IotHubInstallDialogData } from '@home/components/iot-hub/iot-hub-install-dialog.component';
import { TbIotHubUpdateDialogComponent, IotHubUpdateDialogData } from '@home/components/iot-hub/iot-hub-update-dialog.component';
import { TbIotHubDeleteDialogComponent, IotHubDeleteDialogData } from '@home/components/iot-hub/iot-hub-delete-dialog.component';
import { TbDeviceInstallDialogComponent, DeviceInstallDialogData } from '@home/components/iot-hub/device-install-dialog/device-install-dialog.component';
import { IotHubActionsService } from '@home/components/iot-hub/iot-hub-actions.service';
interface SearchResultGroup {
type: ItemType;
@ -83,9 +78,9 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
constructor(
private route: ActivatedRoute,
private router: Router,
private dialog: MatDialog,
private translate: TranslateService,
private iotHubApiService: IotHubApiService
private iotHubApiService: IotHubApiService,
private iotHubActions: IotHubActionsService
) {}
ngOnInit(): void {
@ -212,15 +207,7 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
// Dialogs
openItemDetail(item: MpItemVersionView): void {
const dialogRef = this.dialog.open(TbIotHubItemDetailDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
autoFocus: false,
data: {
item,
installedItem: this.getInstalledItem(item)
} as IotHubItemDetailDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.openItemDetail(item, this.getInstalledItem(item)).subscribe(result => {
if (result === 'installed' || result === 'deleted' || result === 'updated') {
this.reloadInstalledItems();
}
@ -228,56 +215,17 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
}
installItem(item: MpItemVersionView): void {
if (item.type === ItemType.DEVICE) {
this.installDevice(item);
return;
}
const dialogRef = this.dialog.open(TbIotHubInstallDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { item } as IotHubInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.installItem(item).subscribe(result => {
if (result === 'installed') {
this.reloadInstalledItems();
}
});
}
private installDevice(item: MpItemVersionView): void {
this.iotHubApiService.getVersionFileData(item.id as string, { ignoreLoading: true }).subscribe({
next: async (blob: Blob) => {
const zipData = await blob.arrayBuffer();
const dialogRef = this.dialog.open(TbDeviceInstallDialogComponent, {
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
disableClose: true,
autoFocus: false,
data: { item, zipData } as DeviceInstallDialogData
});
dialogRef.afterClosed().subscribe(result => {
if (result === 'installed') {
this.reloadInstalledItems();
}
});
}
});
}
updateItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; }
const dialogRef = this.dialog.open(TbIotHubUpdateDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: {
installedItemId: installedItem.id.id,
itemName: item.name,
itemType: item.type,
version: item.version,
versionId: item.id,
} as IotHubUpdateDialogData
});
dialogRef.afterClosed().subscribe(result => {
this.iotHubActions.updateItem(item, installedItem).subscribe(result => {
if (result === 'updated') {
this.reloadInstalledItems();
}
@ -287,17 +235,8 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; }
const dialogRef = this.dialog.open(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'],
autoFocus: false,
data: { itemName: item.name, itemType: item.type } as IotHubDeleteDialogData
});
dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed) {
this.iotHubApiService.deleteInstalledItem(installedItem.id.id).subscribe(() => {
this.reloadInstalledItems();
});
}
this.iotHubActions.deleteItem(item, installedItem).subscribe(() => {
this.reloadInstalledItems();
});
}

Loading…
Cancel
Save