diff --git a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html index bd7b4a1..7dfe40c 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html +++ b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html @@ -1,15 +1,15 @@ -
@@ -47,7 +47,7 @@
Status
{{ deviceStatusString(deviceSummary) | titlecase}}
+ *ngIf="deviceSummary.smart?.collector_date; else unknownStatus">{{ deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) | titlecase}}
No Data
diff --git a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.spec.ts b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.spec.ts index 3c6cc67..6e578a1 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.spec.ts +++ b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.spec.ts @@ -155,115 +155,4 @@ describe('DashboardDeviceComponent', () => { } as DeviceSummaryModel)).toBe('text-red') }); }) - - - describe('#deviceStatusString()', () => { - - it('if healthy device, should be passing', () => { - httpClientSpy.get.and.returnValue(of({ - settings: { - metrics: { - status_threshold: MetricsStatusThreshold.Both, - } - } - })); - component.ngOnInit() - expect(component.deviceStatusString({ - device: { - device_status: 0 - }, - smart: { - collector_date: moment().subtract(13, 'days').toISOString() - }, - } as DeviceSummaryModel)).toBe('passed') - }); - - it('if device with no smart data, should be unknown', () => { - httpClientSpy.get.and.returnValue(of({ - settings: { - metrics: { - status_threshold: MetricsStatusThreshold.Both, - } - } - })); - component.ngOnInit() - expect(component.deviceStatusString({ - device: { - device_status: 0 - }, - } as DeviceSummaryModel)).toBe('unknown') - }); - - const testCases = [ - { - 'deviceStatus': 1, - 'threshold': MetricsStatusThreshold.Smart, - 'result': 'failed' - }, - { - 'deviceStatus': 1, - 'threshold': MetricsStatusThreshold.Scrutiny, - 'result': 'passed' - }, - { - 'deviceStatus': 1, - 'threshold': MetricsStatusThreshold.Both, - 'result': 'failed' - }, - - { - 'deviceStatus': 2, - 'threshold': MetricsStatusThreshold.Smart, - 'result': 'passed' - }, - { - 'deviceStatus': 2, - 'threshold': MetricsStatusThreshold.Scrutiny, - 'result': 'failed' - }, - { - 'deviceStatus': 2, - 'threshold': MetricsStatusThreshold.Both, - 'result': 'failed' - }, - - { - 'deviceStatus': 3, - 'threshold': MetricsStatusThreshold.Smart, - 'result': 'failed' - }, - { - 'deviceStatus': 3, - 'threshold': MetricsStatusThreshold.Scrutiny, - 'result': 'failed' - }, - { - 'deviceStatus': 3, - 'threshold': MetricsStatusThreshold.Both, - 'result': 'failed' - } - - ] - - testCases.forEach((test, index) => { - it(`if device with status (${test.deviceStatus}) and threshold (${test.threshold}), should be ${test.result}`, () => { - httpClientSpy.get.and.returnValue(of({ - settings: { - metrics: { - status_threshold: test.threshold, - } - } - })); - component.ngOnInit() - expect(component.deviceStatusString({ - device: { - device_status: test.deviceStatus - }, - smart: { - collector_date: moment().subtract(13, 'days').toISOString() - }, - } as DeviceSummaryModel)).toBe(test.result) - }); - }); - }) }); diff --git a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.ts b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.ts index 254f8c1..e29957e 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.ts +++ b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.ts @@ -9,8 +9,7 @@ import {MatDialog} from '@angular/material/dialog'; import {DashboardDeviceDeleteDialogComponent} from 'app/layout/common/dashboard-device-delete-dialog/dashboard-device-delete-dialog.component'; import {DeviceTitlePipe} from 'app/shared/device-title.pipe'; import {DeviceSummaryModel} from 'app/core/models/device-summary-model'; - -export type deviceStatusName = 'unknown' | 'passed' | 'failed' +import {DeviceStatusPipe} from 'app/shared/device-status.pipe'; @Component({ selector: 'app-dashboard-device', @@ -37,6 +36,8 @@ export class DashboardDeviceComponent implements OnInit { readonly humanizeDuration = humanizeDuration; + deviceStatusForModelWithThreshold = DeviceStatusPipe.deviceStatusForModelWithThreshold + ngOnInit(): void { // Subscribe to config changes this._configService.config$ @@ -52,7 +53,7 @@ export class DashboardDeviceComponent implements OnInit { // ----------------------------------------------------------------------------------------------------- classDeviceLastUpdatedOn(deviceSummary: DeviceSummaryModel): string { - const deviceStatus = this.deviceStatusString(deviceSummary) + const deviceStatus = DeviceStatusPipe.deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, this.config.metrics.status_threshold) if (deviceStatus === 'failed') { return 'text-red' // if the device has failed, always highlight in red } else if (deviceStatus === 'passed') { @@ -71,24 +72,6 @@ export class DashboardDeviceComponent implements OnInit { } } - - deviceStatusString(deviceSummary: DeviceSummaryModel): deviceStatusName { - // no smart data, so treat the device status as unknown - if (!deviceSummary.smart) { - return 'unknown' - } - - // determine the device status, by comparing it against the allowed threshold - // tslint:disable-next-line:no-bitwise - const deviceStatus = deviceSummary.device.device_status & this.config.metrics.status_threshold - if (deviceStatus === 0) { - return 'passed' - } else { - return 'failed' - } - } - - openDeleteDialog(): void { const dialogRef = this.dialog.open(DashboardDeviceDeleteDialogComponent, { // width: '250px', diff --git a/webapp/frontend/src/app/modules/detail/detail.component.html b/webapp/frontend/src/app/modules/detail/detail.component.html index e96a493..d2ff848 100644 --- a/webapp/frontend/src/app/modules/detail/detail.component.html +++ b/webapp/frontend/src/app/modules/detail/detail.component.html @@ -56,12 +56,13 @@
- {{device?.device_status | deviceStatus}} + {{device | deviceStatus:!!smart_results:config.metrics.status_threshold:true}}
Status
diff --git a/webapp/frontend/src/app/modules/detail/detail.component.ts b/webapp/frontend/src/app/modules/detail/detail.component.ts index 1353c51..ce21642 100644 --- a/webapp/frontend/src/app/modules/detail/detail.component.ts +++ b/webapp/frontend/src/app/modules/detail/detail.component.ts @@ -16,6 +16,7 @@ import {DeviceModel} from 'app/core/models/device-model'; import {SmartModel} from 'app/core/models/measurements/smart-model'; import {SmartAttributeModel} from 'app/core/models/measurements/smart-attribute-model'; import {AttributeMetadataModel} from 'app/core/models/thresholds/attribute-metadata-model'; +import {DeviceStatusPipe} from 'app/shared/device-status.pipe'; // from Constants.go - these must match const AttributeStatusPassed = 0 @@ -89,6 +90,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { readonly humanizeDuration = humanizeDuration; + deviceStatusForModelWithThreshold = DeviceStatusPipe.deviceStatusForModelWithThreshold // ----------------------------------------------------------------------------------------------------- // @ Lifecycle hooks // ----------------------------------------------------------------------------------------------------- diff --git a/webapp/frontend/src/app/shared/device-status.pipe.spec.ts b/webapp/frontend/src/app/shared/device-status.pipe.spec.ts index 23bc958..57d9e7c 100644 --- a/webapp/frontend/src/app/shared/device-status.pipe.spec.ts +++ b/webapp/frontend/src/app/shared/device-status.pipe.spec.ts @@ -1,8 +1,146 @@ -import { DeviceStatusPipe } from './device-status.pipe'; +import {DeviceStatusPipe} from './device-status.pipe'; +import {MetricsStatusThreshold} from '../core/config/app.config'; +import {DeviceModel} from '../core/models/device-model'; describe('DeviceStatusPipe', () => { - it('create an instance', () => { - const pipe = new DeviceStatusPipe(); - expect(pipe).toBeTruthy(); - }); + it('create an instance', () => { + const pipe = new DeviceStatusPipe(); + expect(pipe).toBeTruthy(); + }); + + describe('#deviceStatusForModelWithThreshold', () => { + it('if healthy device, should be passing', () => { + expect(DeviceStatusPipe.deviceStatusForModelWithThreshold( + {device_status: 0} as DeviceModel, + true, + MetricsStatusThreshold.Both + )).toBe('passed') + }); + + it('if device with no smart data, should be unknown', () => { + expect(DeviceStatusPipe.deviceStatusForModelWithThreshold( + {device_status: 0} as DeviceModel, + false, + MetricsStatusThreshold.Both + )).toBe('unknown') + }); + + const testCases = [ + { + 'deviceStatus': 10000, // invalid status + 'hasSmartResults': false, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': false, + 'result': 'unknown' + }, + + { + 'deviceStatus': 1, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': false, + 'result': 'failed' + }, + { + 'deviceStatus': 1, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Scrutiny, + 'includeReason': false, + 'result': 'passed' + }, + { + 'deviceStatus': 1, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Both, + 'includeReason': false, + 'result': 'failed' + }, + + { + 'deviceStatus': 2, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': false, + 'result': 'passed' + }, + { + 'deviceStatus': 2, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Scrutiny, + 'includeReason': false, + 'result': 'failed' + }, + { + 'deviceStatus': 2, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Both, + 'includeReason': false, + 'result': 'failed' + }, + + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': false, + 'result': 'failed' + }, + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Scrutiny, + 'includeReason': false, + 'result': 'failed' + }, + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Both, + 'includeReason': false, + 'result': 'failed' + }, + + { + 'deviceStatus': 3, + 'hasSmartResults': false, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': true, + 'result': 'unknown' + }, + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Smart, + 'includeReason': true, + 'result': 'failed: smart' + }, + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Scrutiny, + 'includeReason': true, + 'result': 'failed: scrutiny' + }, + { + 'deviceStatus': 3, + 'hasSmartResults': true, + 'threshold': MetricsStatusThreshold.Both, + 'includeReason': true, + 'result': 'failed: both' + } + + + ] + + testCases.forEach((test, index) => { + it(`if device with status (${test.deviceStatus}), hasSmartResults(${test.hasSmartResults}) and threshold (${test.threshold}), should be ${test.result}`, () => { + expect(DeviceStatusPipe.deviceStatusForModelWithThreshold( + {device_status: test.deviceStatus} as DeviceModel, + test.hasSmartResults, + test.threshold, + test.includeReason + )).toBe(test.result) + }); + }); + }); }); diff --git a/webapp/frontend/src/app/shared/device-status.pipe.ts b/webapp/frontend/src/app/shared/device-status.pipe.ts index 42261c6..68a692d 100644 --- a/webapp/frontend/src/app/shared/device-status.pipe.ts +++ b/webapp/frontend/src/app/shared/device-status.pipe.ts @@ -1,21 +1,71 @@ -import { Pipe, PipeTransform } from '@angular/core'; +import {Pipe, PipeTransform} from '@angular/core'; +import {MetricsStatusThreshold} from '../core/config/app.config'; +import {DeviceModel} from '../core/models/device-model'; + +const DEVICE_STATUS_NAMES: { [key: number]: string } = { + 0: 'passed', + 1: 'failed', + 2: 'failed', + 3: 'failed' +}; + +const DEVICE_STATUS_NAMES_WITH_REASON: { [key: number]: string } = { + 0: 'passed', + 1: 'failed: smart', + 2: 'failed: scrutiny', + 3: 'failed: both' +}; + @Pipe({ - name: 'deviceStatus' + name: 'deviceStatus' }) export class DeviceStatusPipe implements PipeTransform { - transform(deviceStatusFlag: number): string { - if(deviceStatusFlag === 0){ - return 'passed' - } else if(deviceStatusFlag === 3){ - return 'failed: both' - } else if(deviceStatusFlag === 2) { - return 'failed: scrutiny' - } else if(deviceStatusFlag === 1) { - return 'failed: smart' - } - return 'unknown' - } + + static deviceStatusForModelWithThreshold( + deviceModel: DeviceModel, + hasSmartResults: boolean = true, + threshold: MetricsStatusThreshold = MetricsStatusThreshold.Both, + includeReason: boolean = false + ): string { + // no smart data, so treat the device status as unknown + if (!hasSmartResults) { + return 'unknown' + } + + let statusNameLookup = DEVICE_STATUS_NAMES + if (includeReason) { + statusNameLookup = DEVICE_STATUS_NAMES_WITH_REASON + } + // determine the device status, by comparing it against the allowed threshold + // tslint:disable-next-line:no-bitwise + const deviceStatus = deviceModel.device_status & threshold + return statusNameLookup[deviceStatus] + } + + // static deviceStatusForModelWithThreshold(deviceModel: DeviceModel | any, threshold: MetricsStatusThreshold): string { + // // tslint:disable-next-line:no-bitwise + // const deviceStatus = deviceModel?.device_status & threshold + // if(deviceStatus === 0){ + // return 'passed' + // } else if(deviceStatus === 3){ + // return 'failed: both' + // } else if(deviceStatus === 2) { + // return 'failed: scrutiny' + // } else if(deviceStatus === 1) { + // return 'failed: smart' + // } + // return 'unknown' + // } + + transform( + deviceModel: DeviceModel, + hasSmartResults: boolean = true, + threshold: MetricsStatusThreshold = MetricsStatusThreshold.Both, + includeReason: boolean = false + ): string { + return DeviceStatusPipe.deviceStatusForModelWithThreshold(deviceModel, hasSmartResults, threshold, includeReason) + } }