From 4efc5ea9d1d72a1beffffee2196a36d0088d9120 Mon Sep 17 00:00:00 2001 From: Alex Karpov Date: Tue, 10 Sep 2024 12:02:24 +0300 Subject: [PATCH] NAS-130980 / 25.04 / ElectricEel HA Dashboard is missing the Standby System Information card. (#10640) --- .../dashboard/dashboard.component.spec.ts | 4 ++-- .../dashboard/dashboard.component.ts | 11 ++++++--- .../services/dashboard.store.spec.ts | 14 +++++++++-- .../dashboard/services/dashboard.store.ts | 23 ++++++++++++------- .../services/get-default-widgets.spec.ts | 21 +++++++++++++++++ ...ets.constant.ts => get-default-widgets.ts} | 18 ++++++++++++++- 6 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 src/app/pages/dashboard/services/get-default-widgets.spec.ts rename src/app/pages/dashboard/services/{default-widgets.constant.ts => get-default-widgets.ts} (72%) diff --git a/src/app/pages/dashboard/components/dashboard/dashboard.component.spec.ts b/src/app/pages/dashboard/components/dashboard/dashboard.component.spec.ts index 4355607f0ec..c4a52629b72 100644 --- a/src/app/pages/dashboard/components/dashboard/dashboard.component.spec.ts +++ b/src/app/pages/dashboard/components/dashboard/dashboard.component.spec.ts @@ -18,7 +18,7 @@ import { import { WidgetGroupComponent } from 'app/pages/dashboard/components/widget-group/widget-group.component'; import { WidgetGroupFormComponent } from 'app/pages/dashboard/components/widget-group-form/widget-group-form.component'; import { DashboardStore } from 'app/pages/dashboard/services/dashboard.store'; -import { defaultWidgets } from 'app/pages/dashboard/services/default-widgets.constant'; +import { getDefaultWidgets } from 'app/pages/dashboard/services/get-default-widgets'; import { WidgetGroup, WidgetGroupLayout } from 'app/pages/dashboard/types/widget-group.interface'; import { ChainedComponentResponse, IxChainedSlideInService } from 'app/services/ix-chained-slide-in.service'; @@ -160,7 +160,7 @@ describe('DashboardComponent', () => { const saveButton = await loader.getHarness(MatButtonHarness.with({ text: 'Save' })); await saveButton.click(); - expect(spectator.inject(DashboardStore).save).toHaveBeenCalledWith(defaultWidgets); + expect(spectator.inject(DashboardStore).save).toHaveBeenCalledWith(getDefaultWidgets()); expect(spectator.inject(SnackbarService).success).toHaveBeenCalledWith('Dashboard settings saved'); }); diff --git a/src/app/pages/dashboard/components/dashboard/dashboard.component.ts b/src/app/pages/dashboard/components/dashboard/dashboard.component.ts index 41fc0d3e80c..7bb63f24ccc 100644 --- a/src/app/pages/dashboard/components/dashboard/dashboard.component.ts +++ b/src/app/pages/dashboard/components/dashboard/dashboard.component.ts @@ -7,6 +7,7 @@ import { } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { isEqual } from 'lodash-es'; import { EmptyType } from 'app/enums/empty-type.enum'; @@ -15,10 +16,12 @@ import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service' import { dashboardElements } from 'app/pages/dashboard/components/dashboard/dashboard.elements'; import { WidgetGroupFormComponent } from 'app/pages/dashboard/components/widget-group-form/widget-group-form.component'; import { DashboardStore } from 'app/pages/dashboard/services/dashboard.store'; -import { defaultWidgets } from 'app/pages/dashboard/services/default-widgets.constant'; +import { getDefaultWidgets } from 'app/pages/dashboard/services/get-default-widgets'; import { WidgetGroup } from 'app/pages/dashboard/types/widget-group.interface'; import { ErrorHandlerService } from 'app/services/error-handler.service'; import { ChainedComponentResponse, IxChainedSlideInService } from 'app/services/ix-chained-slide-in.service'; +import { AppsState } from 'app/store'; +import { selectIsHaLicensed } from 'app/store/ha-info/ha-info.selectors'; @UntilDestroy() @Component({ @@ -43,13 +46,14 @@ export class DashboardComponent implements OnInit { readonly renderedGroups = signal([]); readonly savedGroups = toSignal(this.dashboardStore.groups$); readonly isLoading = toSignal(this.dashboardStore.isLoading$); + readonly isHaLicensed = toSignal(this.store$.select(selectIsHaLicensed)); readonly isLoadingFirstTime = computed(() => this.isLoading() && this.savedGroups() === null); readonly newFeatureConfig = { key: 'dashboardConfigure', message: this.translate.instant('New widgets and layouts.'), }; readonly customLayout = computed(() => { - return !isEqual(this.renderedGroups(), defaultWidgets); + return !isEqual(this.renderedGroups(), getDefaultWidgets(this.isHaLicensed())); }); emptyDashboardConf: EmptyConfig = { @@ -66,6 +70,7 @@ export class DashboardComponent implements OnInit { private errorHandler: ErrorHandlerService, private translate: TranslateService, private snackbar: SnackbarService, + private store$: Store, ) {} ngOnInit(): void { @@ -142,7 +147,7 @@ export class DashboardComponent implements OnInit { } protected onReset(): void { - this.renderedGroups.set(defaultWidgets); + this.renderedGroups.set(getDefaultWidgets(this.isHaLicensed())); this.snackbar.success(this.translate.instant('Default widgets restored')); } diff --git a/src/app/pages/dashboard/services/dashboard.store.spec.ts b/src/app/pages/dashboard/services/dashboard.store.spec.ts index 9b388dc779d..e74873e420b 100644 --- a/src/app/pages/dashboard/services/dashboard.store.spec.ts +++ b/src/app/pages/dashboard/services/dashboard.store.spec.ts @@ -1,11 +1,13 @@ import { createServiceFactory, SpectatorService, mockProvider } from '@ngneat/spectator/jest'; +import { provideMockStore } from '@ngrx/store/testing'; import { firstValueFrom, of } from 'rxjs'; import { mockCall, mockWebSocket } from 'app/core/testing/utils/mock-websocket.utils'; -import { defaultWidgets } from 'app/pages/dashboard/services/default-widgets.constant'; +import { getDefaultWidgets } from 'app/pages/dashboard/services/get-default-widgets'; import { WidgetGroupLayout } from 'app/pages/dashboard/types/widget-group.interface'; import { WidgetType } from 'app/pages/dashboard/types/widget.interface'; import { AuthService } from 'app/services/auth/auth.service'; import { WebSocketService } from 'app/services/ws.service'; +import { selectIsHaLicensed } from 'app/store/ha-info/ha-info.selectors'; import { DashboardStore, initialState } from './dashboard.store'; const initialGroups = [ @@ -35,6 +37,14 @@ describe('DashboardStore', () => { }, }), }), + provideMockStore({ + selectors: [ + { + selector: selectIsHaLicensed, + value: true, + }, + ], + }), mockWebSocket([ mockCall('auth.set_attribute'), ]), @@ -105,7 +115,7 @@ describe('DashboardStore', () => { expect(await firstValueFrom(spectator.service.state$)).toMatchObject({ ...initialState, - groups: defaultWidgets, + groups: getDefaultWidgets(true), }); }); diff --git a/src/app/pages/dashboard/services/dashboard.store.ts b/src/app/pages/dashboard/services/dashboard.store.ts index ec7a130a8c2..003de104925 100644 --- a/src/app/pages/dashboard/services/dashboard.store.ts +++ b/src/app/pages/dashboard/services/dashboard.store.ts @@ -1,17 +1,20 @@ import { Injectable } from '@angular/core'; import { UntilDestroy } from '@ngneat/until-destroy'; import { ComponentStore } from '@ngrx/component-store'; +import { Store } from '@ngrx/store'; import { EMPTY, - Observable, catchError, filter, finalize, map, of, switchMap, tap, + Observable, catchError, combineLatest, filter, finalize, map, of, switchMap, tap, } from 'rxjs'; import { WidgetName } from 'app/enums/widget-name.enum'; -import { defaultWidgets } from 'app/pages/dashboard/services/default-widgets.constant'; +import { getDefaultWidgets } from 'app/pages/dashboard/services/get-default-widgets'; import { WidgetGroup, WidgetGroupLayout } from 'app/pages/dashboard/types/widget-group.interface'; import { SomeWidgetSettings, WidgetType } from 'app/pages/dashboard/types/widget.interface'; import { AuthService } from 'app/services/auth/auth.service'; import { ErrorHandlerService } from 'app/services/error-handler.service'; import { WebSocketService } from 'app/services/ws.service'; +import { AppsState } from 'app/store'; +import { selectIsHaLicensed } from 'app/store/ha-info/ha-info.selectors'; // we have external `DashConfigItem` in old dashboard, but it will be removed once we go ahead with new dashboard export interface OldDashboardConfigItem { @@ -50,6 +53,7 @@ export class DashboardStore extends ComponentStore { private authService: AuthService, private ws: WebSocketService, private errorHandler: ErrorHandlerService, + private store$: Store, ) { super(initialState); } @@ -64,15 +68,18 @@ export class DashboardStore extends ComponentStore { } return this.authService.refreshUser(); }), - switchMap(() => this.authService.user$.pipe( - filter(Boolean), - map((user) => user.attributes.dashState), - )), - tap((dashState) => { + switchMap(() => combineLatest([ + this.authService.user$.pipe( + filter(Boolean), + map((user) => user.attributes.dashState), + ), + this.store$.select(selectIsHaLicensed), + ])), + tap(([dashState, isHaLicensed]) => { this.setState({ isLoading: false, globalError: '', - groups: this.getDashboardGroups(dashState || defaultWidgets), + groups: this.getDashboardGroups(dashState || getDefaultWidgets(isHaLicensed)), }); }), catchError((error) => { diff --git a/src/app/pages/dashboard/services/get-default-widgets.spec.ts b/src/app/pages/dashboard/services/get-default-widgets.spec.ts new file mode 100644 index 00000000000..92b63a1c9df --- /dev/null +++ b/src/app/pages/dashboard/services/get-default-widgets.spec.ts @@ -0,0 +1,21 @@ +import { getDefaultWidgets } from 'app/pages/dashboard/services/get-default-widgets'; +import { WidgetGroup } from 'app/pages/dashboard/types/widget-group.interface'; +import { WidgetType } from 'app/pages/dashboard/types/widget.interface'; + +describe('getDefaultWidgets', () => { + it('should return all default widgets when isHaLicensed is true', () => { + const result: WidgetGroup[] = getDefaultWidgets(true); + + expect(result).toHaveLength(9); + expect(result[0].slots[0].type).toBe(WidgetType.SystemInfoActive); + expect(result[1].slots[0].type).toBe(WidgetType.SystemInfoPassive); + }); + + it('should return default widgets without the second widget when isHaLicensed is false', () => { + const result: WidgetGroup[] = getDefaultWidgets(false); + + expect(result).toHaveLength(8); + expect(result[0].slots[0].type).toBe(WidgetType.SystemInfoActive); + expect(result[1].slots[0].type).toBe(WidgetType.CpuModelWidget); + }); +}); diff --git a/src/app/pages/dashboard/services/default-widgets.constant.ts b/src/app/pages/dashboard/services/get-default-widgets.ts similarity index 72% rename from src/app/pages/dashboard/services/default-widgets.constant.ts rename to src/app/pages/dashboard/services/get-default-widgets.ts index 43bd4a1edd2..777a28071d8 100644 --- a/src/app/pages/dashboard/services/default-widgets.constant.ts +++ b/src/app/pages/dashboard/services/get-default-widgets.ts @@ -1,13 +1,19 @@ import { WidgetGroup, WidgetGroupLayout } from 'app/pages/dashboard/types/widget-group.interface'; import { WidgetType } from 'app/pages/dashboard/types/widget.interface'; -export const defaultWidgets: WidgetGroup[] = [ +const defaultWidgets: WidgetGroup[] = [ { layout: WidgetGroupLayout.Full, slots: [ { type: WidgetType.SystemInfoActive }, ], }, + { + layout: WidgetGroupLayout.Full, + slots: [ + { type: WidgetType.SystemInfoPassive }, + ], + }, { layout: WidgetGroupLayout.Halves, slots: [ @@ -54,3 +60,13 @@ export const defaultWidgets: WidgetGroup[] = [ ], }, ]; + +export const getDefaultWidgets = (isHaLicensed?: boolean): WidgetGroup[] => { + if (!isHaLicensed) { + return defaultWidgets.filter( + (widgetGroup) => !widgetGroup.slots.some((slot) => slot.type === WidgetType.SystemInfoPassive), + ); + } + + return defaultWidgets; +};