Browse Source

feat(iot-hub): replace vendor text input with checkbox filter from API

Fetch distinct vendor list from IoT Hub API (GET /versions/published/vendors)
when DEVICE tab is activated. Render as checkboxes (same pattern as
Hardware Type filter). Vendor is the first filter section for devices.

Replaces the debounced text input approach with dynamic checkbox list.
pull/15508/head
Andrii Shvaika 3 months ago
parent
commit
10645be679
  1. 7
      ui-ngx/src/app/core/http/iot-hub-api.service.ts
  2. 29
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html
  3. 52
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

7
ui-ngx/src/app/core/http/iot-hub-api.service.ts

@ -84,6 +84,13 @@ export class IotHubApiService {
); );
} }
public getPublishedDeviceVendors(config?: IotHubRequestConfig): Observable<string[]> {
return this.http.get<string[]>(
`${this.baseUrl}/api/versions/published/vendors`,
{ params: this.buildParams(config) }
);
}
public getVersionInfo(versionId: string, config?: IotHubRequestConfig): Observable<MpItemVersionView> { public getVersionInfo(versionId: string, config?: IotHubRequestConfig): Observable<MpItemVersionView> {
return this.http.get<MpItemVersionView>( return this.http.get<MpItemVersionView>(
`${this.baseUrl}/api/versions/${versionId}`, `${this.baseUrl}/api/versions/${versionId}`,

29
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.html

@ -76,17 +76,12 @@
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title>{{ 'iot-hub.vendor' | translate }}</mat-panel-title> <mat-panel-title>{{ 'iot-hub.vendor' | translate }}</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<mat-form-field appearance="outline" class="tb-vendor-filter-field"> @for (v of availableVendors; track v) {
<input matInput <mat-checkbox [checked]="isVendorActive(v)"
[value]="vendorFilter" (change)="onVendorToggle(v)">
(input)="onVendorInput($event)" {{ v }}
placeholder="{{ 'iot-hub.vendor-placeholder' | translate }}"> </mat-checkbox>
@if (vendorFilter) { }
<button mat-icon-button matSuffix (click)="clearVendorFilter()">
<mat-icon>close</mat-icon>
</button>
}
</mat-form-field>
</mat-expansion-panel> </mat-expansion-panel>
<div class="tb-iot-hub-filter-divider"></div> <div class="tb-iot-hub-filter-divider"></div>
<mat-expansion-panel [expanded]="true" class="tb-iot-hub-filter-panel"> <mat-expansion-panel [expanded]="true" class="tb-iot-hub-filter-panel">
@ -193,6 +188,12 @@
<mat-icon matChipRemove>close</mat-icon> <mat-icon matChipRemove>close</mat-icon>
</mat-chip> </mat-chip>
} }
@for (v of getActiveVendorsArray(); track v) {
<mat-chip (removed)="onVendorToggle(v)">
{{ v }}
<mat-icon matChipRemove>close</mat-icon>
</mat-chip>
}
@for (key of getActiveUseCasesArray(); track key) { @for (key of getActiveUseCasesArray(); track key) {
<mat-chip (removed)="onUseCaseToggle(key)"> <mat-chip (removed)="onUseCaseToggle(key)">
{{ getUseCaseLabel(key) }} {{ getUseCaseLabel(key) }}
@ -277,6 +278,12 @@
<mat-icon matChipRemove>close</mat-icon> <mat-icon matChipRemove>close</mat-icon>
</mat-chip> </mat-chip>
} }
@for (v of getActiveVendorsArray(); track v) {
<mat-chip (removed)="onVendorToggle(v)">
{{ v }}
<mat-icon matChipRemove>close</mat-icon>
</mat-chip>
}
@for (key of getActiveUseCasesArray(); track key) { @for (key of getActiveUseCasesArray(); track key) {
<mat-chip (removed)="onUseCaseToggle(key)"> <mat-chip (removed)="onUseCaseToggle(key)">
{{ getUseCaseLabel(key) }} {{ getUseCaseLabel(key) }}

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

@ -96,8 +96,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
activeRuleChainTypes = new Set<string>(); activeRuleChainTypes = new Set<string>();
activeConnectivity = new Set<string>(); activeConnectivity = new Set<string>();
activeHardwareTypes = new Set<string>(); activeHardwareTypes = new Set<string>();
vendorFilter = ''; activeVendors = new Set<string>();
private vendorDebounceTimer: any; availableVendors: string[] = [];
sortOptions: SortOption[] = [ sortOptions: SortOption[] = [
{ value: 'totalInstallCount', label: 'iot-hub.sort-most-installed', direction: Direction.DESC }, { value: 'totalInstallCount', label: 'iot-hub.sort-most-installed', direction: Direction.DESC },
@ -152,6 +152,8 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
this.loadInstalledWidgets(); this.loadInstalledWidgets();
} else if (this.activeType === ItemType.SOLUTION_TEMPLATE) { } else if (this.activeType === ItemType.SOLUTION_TEMPLATE) {
this.loadInstalledSolutionTemplates(); this.loadInstalledSolutionTemplates();
} else if (this.activeType === ItemType.DEVICE) {
this.loadVendors();
} }
this.loadItems(); this.loadItems();
} }
@ -183,13 +185,15 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
this.activeRuleChainTypes.clear(); this.activeRuleChainTypes.clear();
this.activeConnectivity.clear(); this.activeConnectivity.clear();
this.activeHardwareTypes.clear(); this.activeHardwareTypes.clear();
this.vendorFilter = ''; this.activeVendors.clear();
this.updateCategories(); this.updateCategories();
this.pageIndex = 0; this.pageIndex = 0;
if (type === ItemType.WIDGET) { if (type === ItemType.WIDGET) {
this.loadInstalledWidgets(); this.loadInstalledWidgets();
} else if (type === ItemType.SOLUTION_TEMPLATE) { } else if (type === ItemType.SOLUTION_TEMPLATE) {
this.loadInstalledSolutionTemplates(); this.loadInstalledSolutionTemplates();
} else if (type === ItemType.DEVICE) {
this.loadVendors();
} }
this.loadItems(); this.loadItems();
} }
@ -322,22 +326,24 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
return this.activeHardwareTypes.has(value); return this.activeHardwareTypes.has(value);
} }
onVendorInput(event: Event): void { onVendorToggle(vendor: string): void {
const value = (event.target as HTMLInputElement).value?.trim() || ''; if (this.activeVendors.has(vendor)) {
this.vendorFilter = value; this.activeVendors.delete(vendor);
clearTimeout(this.vendorDebounceTimer); } else {
this.vendorDebounceTimer = setTimeout(() => { this.activeVendors.add(vendor);
this.pageIndex = 0; }
this.loadItems();
}, 400);
}
clearVendorFilter(): void {
this.vendorFilter = '';
this.pageIndex = 0; this.pageIndex = 0;
this.loadItems(); this.loadItems();
} }
isVendorActive(vendor: string): boolean {
return this.activeVendors.has(vendor);
}
getActiveVendorsArray(): string[] {
return Array.from(this.activeVendors);
}
getActiveConnectivityArray(): string[] { getActiveConnectivityArray(): string[] {
return Array.from(this.activeConnectivity); return Array.from(this.activeConnectivity);
} }
@ -442,7 +448,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
this.activeRuleChainTypes.clear(); this.activeRuleChainTypes.clear();
this.activeConnectivity.clear(); this.activeConnectivity.clear();
this.activeHardwareTypes.clear(); this.activeHardwareTypes.clear();
this.vendorFilter = ''; this.activeVendors.clear();
if (this.fixedSubType) { if (this.fixedSubType) {
this.getActiveSubtypes()?.add(this.fixedSubType); this.getActiveSubtypes()?.add(this.fixedSubType);
} }
@ -455,7 +461,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
get activeFilterCount(): number { get activeFilterCount(): number {
const subtypeCount = this.fixedSubType ? this.getActiveSubtypesArray().length : (this.getActiveSubtypes()?.size || 0); const subtypeCount = this.fixedSubType ? this.getActiveSubtypesArray().length : (this.getActiveSubtypes()?.size || 0);
return subtypeCount + this.activeCategories.size + this.activeUseCases.size + return subtypeCount + this.activeCategories.size + this.activeUseCases.size +
this.activeConnectivity.size + this.activeHardwareTypes.size + (this.vendorFilter ? 1 : 0); this.activeConnectivity.size + this.activeHardwareTypes.size + this.activeVendors.size;
} }
hasActiveDropdownFilters(): boolean { hasActiveDropdownFilters(): boolean {
@ -463,7 +469,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
return this.activeCategories.size > 0 || return this.activeCategories.size > 0 ||
this.activeUseCases.size > 0 || subtypeCount > 0 || this.activeUseCases.size > 0 || subtypeCount > 0 ||
this.activeConnectivity.size > 0 || this.activeHardwareTypes.size > 0 || this.activeConnectivity.size > 0 || this.activeHardwareTypes.size > 0 ||
!!this.vendorFilter; this.activeVendors.size > 0;
} }
hasActiveFilters(): boolean { hasActiveFilters(): boolean {
@ -573,6 +579,14 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
}); });
} }
private loadVendors(): void {
this.iotHubApiService.getPublishedDeviceVendors({ignoreLoading: true, ignoreErrors: true}).subscribe({
next: (vendors) => {
this.availableVendors = vendors?.sort() || [];
}
});
}
private loadInstalledSolutionTemplates(): void { private loadInstalledSolutionTemplates(): void {
if (this.installedSolutionTemplates !== null) { if (this.installedSolutionTemplates !== null) {
return; return;
@ -622,7 +636,7 @@ export class TbIotHubBrowseComponent implements OnInit, OnDestroy {
undefined, undefined,
this.activeHardwareTypes.size > 0 ? Array.from(this.activeHardwareTypes) : undefined, this.activeHardwareTypes.size > 0 ? Array.from(this.activeHardwareTypes) : undefined,
this.activeConnectivity.size > 0 ? Array.from(this.activeConnectivity) : undefined, this.activeConnectivity.size > 0 ? Array.from(this.activeConnectivity) : undefined,
this.vendorFilter || undefined this.activeVendors.size === 1 ? Array.from(this.activeVendors)[0] : undefined
); );
this.iotHubApiService.getPublishedVersions( this.iotHubApiService.getPublishedVersions(
query, query,

Loading…
Cancel
Save