Skip to content

Commit

Permalink
consolidate device status to string logic in DeviceStatusPipe.
Browse files Browse the repository at this point in the history
Ensure device status takes into account new settings.
  • Loading branch information
AnalogJ committed Jul 29, 2022
1 parent 2e768fb commit ce2f990
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 159 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<div [ngClass]="{ 'border-green': deviceStatusString(deviceSummary) == 'passed',
'border-red': deviceStatusString(deviceSummary) == 'failed' }"
<div [ngClass]="{ 'border-green': deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) == 'passed',
'border-red': deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) == 'failed' }"
class="relative flex flex-col flex-auto p-6 pr-3 pb-3 bg-card rounded border-l-4 shadow-md overflow-hidden">
<div class="absolute bottom-0 right-0 w-24 h-24 -m-6">
<mat-icon class="icon-size-96 opacity-12 text-green"
*ngIf="deviceStatusString(deviceSummary) == 'passed'"
*ngIf="deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) == 'passed'"
[svgIcon]="'heroicons_outline:check-circle'"></mat-icon>
<mat-icon class="icon-size-96 opacity-12 text-red"
*ngIf="deviceStatusString(deviceSummary) == 'failed'"
*ngIf="deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) == 'failed'"
[svgIcon]="'heroicons_outline:exclamation-circle'"></mat-icon>
<mat-icon class="icon-size-96 opacity-12 text-yellow"
*ngIf="deviceStatusString(deviceSummary) == 'unknown'"
*ngIf="deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) == 'unknown'"
[svgIcon]="'heroicons_outline:question-mark-circle'"></mat-icon>
</div>
<div class="flex items-center">
Expand Down Expand Up @@ -47,7 +47,7 @@
<div class="flex flex-col mx-6 my-3 xs:w-full">
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Status</div>
<div class="mt-2 font-medium text-3xl leading-none"
*ngIf="deviceSummary.smart?.collector_date; else unknownStatus">{{ deviceStatusString(deviceSummary) | titlecase}}</div>
*ngIf="deviceSummary.smart?.collector_date; else unknownStatus">{{ deviceStatusForModelWithThreshold(deviceSummary.device, !!deviceSummary.smart, config.metrics.status_threshold) | titlecase}}</div>
<ng-template #unknownStatus><div class="mt-2 font-medium text-3xl leading-none">No Data</div></ng-template>
</div>
<div class="flex flex-col mx-6 my-3 xs:w-full">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
});
});
})
});
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -37,6 +36,8 @@ export class DashboardDeviceComponent implements OnInit {

readonly humanizeDuration = humanizeDuration;

deviceStatusForModelWithThreshold = DeviceStatusPipe.deviceStatusForModelWithThreshold

ngOnInit(): void {
// Subscribe to config changes
this._configService.config$
Expand All @@ -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') {
Expand All @@ -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',
Expand Down
5 changes: 3 additions & 2 deletions webapp/frontend/src/app/modules/detail/detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ <h2 class="m-0">Drive Details - {{device | deviceTitle:config.dashboard_display}
<div *ngIf="device" class="my-2 col-span-2 lt-md:col-span-1">
<div>
<span class="inline-flex items-center font-bold text-xs px-2 py-2px rounded-full tracking-wide uppercase"
[ngClass]="{'red-200': device?.device_status != 0,
[ngClass]="{'red-200': deviceStatusForModelWithThreshold(device, !!smart_results, config.metrics.status_threshold) == 'failed',
'green-200': device?.device_status == 0}">
<span class="w-2 h-2 rounded-full mr-2"
[ngClass]="{'bg-red': device?.device_status != 0,
'bg-green': device?.device_status == 0}"></span>
<span class="pr-2px leading-relaxed whitespace-no-wrap">{{device?.device_status | deviceStatus}}</span>
<span
class="pr-2px leading-relaxed whitespace-no-wrap">{{device | deviceStatus:!!smart_results:config.metrics.status_threshold:true}}</span>
</span>
</div>
<div class="text-secondary text-md">Status</div>
Expand Down
2 changes: 2 additions & 0 deletions webapp/frontend/src/app/modules/detail/detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,6 +90,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {

readonly humanizeDuration = humanizeDuration;

deviceStatusForModelWithThreshold = DeviceStatusPipe.deviceStatusForModelWithThreshold
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
Expand Down
148 changes: 143 additions & 5 deletions webapp/frontend/src/app/shared/device-status.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
});
});
});
});
Loading

0 comments on commit ce2f990

Please sign in to comment.