Skip to content

Commit

Permalink
feat(netflix): Improve Availability Module Context
Browse files Browse the repository at this point in the history
  • Loading branch information
jrsquared committed Mar 22, 2017
1 parent 5e9c044 commit dfc6d8a
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 55 deletions.
Expand Up @@ -5,37 +5,42 @@

<div class="dropdown-menu availability-graphs" uib-dropdown-menu>
<div class="heading">
<h4>Netflix Service Availability Status <help-field key="availability.context"></help-field></h4>
<h4>Netflix Streaming Availability Status <help-field key="availability.context"></help-field></h4>
</div>
<div class="row">
<div class="section current">
<h5>Recent</h5>
<h6>Reason</h6>
<div class="current-score aggregate-score-{{aggregate.score}}"><span ng-bind-html="aggregate.reason"></span></div>
<h6>Yesterday <help-field content="The availability of the Netflix service from {{yesterday.date_range[0] | date:'short'}} to {{yesterday.date_range[1] | date:'short'}} has a target of {{yesterday.target_nines}} nines."></help-field></h6>
<div class="section-title">Average</div>
<div class="section-title">REASON</div>
<div class="current-container">
<div class="current-score aggregate-score-{{aggregate.score}}"></div>
<div class="current-reason">
<div class="current-reason-header score-color-{{aggregate.score}}">{{aggregate.title}}</div>
<div class="current-reason-content" ng-bind-html="aggregate.reason"></div>
</div>
</div>
<div class="section-title donut">YESTERDAY'S<br/>AVG <help-field content="The availability of the Netflix service from {{yesterday.date_range[0] | date:'short'}} to {{yesterday.date_range[1] | date:'short'}} has a target of {{yesterday.target_nines}} nines."></help-field></div>
<availability-donut nines="yesterday.nines" target-nines="yesterday.target_nines" score="yesterday.score" availability="yesterday.availability" outer-radius="100"></availability-donut>
</div>
<div class="section past">
<div class="row">
<h5>Last 28 Days <help-field content="The availability of the Netflix service from {{twentyeightdays.date_range[0] | date:'shortDate'}} to {{twentyeightdays.date_range[1] | date:'shortDate'}} has a target of {{twentyeightdays.target_nines}} nines."></h5>
<div class="sub-section average">
<div class="section-title">Average</div>
<div class="section-title">AVERAGE</div>
<availability-donut nines="twentyeightdays.nines" target-nines="twentyeightdays.target_nines" score="twentyeightdays.score" availability="twentyeightdays.availability" outer-radius="60"></availability-donut>
</div>
<div class="sub-section trend">
<div class="section-title">Trend</div>
<div class="section-title">TREND</div>
<availability-trend availability-window="twentyeightdays" width="200" height="80"></availability-trend>
</div>
</div>
<div class="row">
<h5>Last 91 Days <help-field content="The availability of the Netflix service from {{ninetyonedays.date_range[0] | date:'shortDate'}} to {{ninetyonedays.date_range[1] | date:'shortDate'}} has a target of {{ninetyonedays.target_nines}} nines."></h5>
<div class="sub-section average">
<div class="section-title">Average</div>
<div class="section-title">AVERAGE</div>
<availability-donut nines="ninetyonedays.nines" target-nines="ninetyonedays.target_nines" score="ninetyonedays.score" availability="ninetyonedays.availability" outer-radius="60"></availability-donut>
</div>
<div class="sub-section trend">
<div class="section-title">Trend</div>
<div class="section-title">TREND</div>
<availability-trend availability-window="ninetyonedays" width="200" height="80"></availability-trend>
</div>
</div>
Expand Down
14 changes: 10 additions & 4 deletions app/scripts/modules/netflix/availability/availability.directive.ts
Expand Up @@ -9,6 +9,7 @@ import './availability.less';

interface IAggregateDetails {
score: number;
title: string;
reason: string;
}

Expand All @@ -29,6 +30,7 @@ export class AvailabilityController implements ng.IComponentController {

private getAggregateScore (result: IAvailabilityData): IAggregateDetails {
let score = 1;
let title = 'Stable';
let reason = 'ALL AVAILABILITY<br/>GOALS MET!';

// Figure out score
Expand All @@ -38,23 +40,27 @@ export class AvailabilityController implements ng.IComponentController {
} else if (result.trends.yesterday.score > 1) {
// If there were recent incidents yesterday
score = 4;
reason = 'Yesterday\'s GOAL <strong>NOT</strong> MET';
title = 'Danger';
reason = 'Yesterday\'s goal <strong>not</strong> met';
// TODO: Add incident links to details
} else if (result.trends['28days'].score > 1 && result.trends['91days'].score > 1) {
// If we have not acheived 28 day availability goals
score = 3;
reason = '28 DAY GOAL AND <br/>91 DAY GOAL <strong>NOT</strong> MET';
title = 'Warning';
reason = '28 day goal and 91 day goal <strong>not</strong> met';
} else if (result.trends['28days'].score > 1) {
// If we have not acheived 28 day availability goals (but rest are acheived)
score = 2;
title = 'Warning';
reason = '28 DAY GOAL <strong>NOT</strong> MET';
} else if (result.trends['91days'].score > 1) {
// If we have not acheived 90 day availability goals (but rest are acheived)
score = 2;
reason = '91 DAY GOAL <strong>NOT</strong> MET';
title = 'Warning';
reason = '91 day goal <strong>not</strong> met';
}

return { score, reason };
return { score, title, reason };
}

public refreshData(): void {
Expand Down
@@ -1,20 +1,32 @@
import { module } from 'angular';
import { Arc, arc, DefaultArcObject, Pie, pie } from 'd3-shape';
import { Arc, arc, DefaultArcObject } from 'd3-shape';
import { scalePow } from 'd3-scale';

import './availability.less';

interface ArcData {
interface IArcData {
path: string;
score: number;
}

interface ITargetData {
availability: number;
path: string;
rotation: number;
labelPosition: [number, number];
score: number;
}

interface IDonutGraphData {
arcs: ArcData[];
arcs: IArcData[];
targets: ITargetData[];
total: string;
width: number;
height: number;
}

const maxNines = 4.6;

export class AvailabilityDonutController implements ng.IComponentController {
public availability: number;
public donut: IDonutGraphData;
Expand All @@ -26,11 +38,23 @@ export class AvailabilityDonutController implements ng.IComponentController {
private targetNines: number;
private outerRadius: number;
private arc: Arc<any, DefaultArcObject> = arc();
private pie: Pie<any, number> = pie<number>().sort(null).sortValues(null);
private donutWidthPercent = 0.7;

// inflection points for changing the scale of the donut
private inflectionDomains = [ [0, 2], [2, maxNines] ];
private inflectionRange = 360 / this.inflectionDomains.length;
private inflectionRanges = this.inflectionDomains.map((_, i) => [ this.inflectionRange * i, this.inflectionRange * (i + 1) ]);
private inflectionRangesRadians = this.inflectionRanges.map((range) => [ range[0] * (Math.PI / 180), range[1] * (Math.PI / 180) ]);

private scaleTargets(target: number, radians = false): number {
const inflectionIndex = this.inflectionDomains.findIndex((domain) => domain[0] < target && target < domain[1]);
const inflectionRanges = radians ? this.inflectionRangesRadians : this.inflectionRanges;

return scalePow().domain(this.inflectionDomains[inflectionIndex]).range(inflectionRanges[inflectionIndex])(target);
}

private updateData(): void {
if (this.targetNines && this.nines && this.outerRadius) {
if (this.nines && this.outerRadius) {
this.donut = this.buildDonutGraph();
this.ninesSize = this.outerRadius * 0.46;
this.percentSize = this.outerRadius * 0.2;
Expand All @@ -47,15 +71,14 @@ export class AvailabilityDonutController implements ng.IComponentController {
}

private buildDonutGraph(): IDonutGraphData {
const totalNines = Math.min(this.nines, this.targetNines);
const pieData = [totalNines, this.targetNines - totalNines];
const availabilityPie = this.pie(pieData);
const arcs: ArcData[] = [
const currentNines = Math.min(this.nines, maxNines);
const angle = this.scaleTargets(currentNines, true) - 0.02;
const arcs: IArcData[] = [
// Availability arc
{
path: this.arc({
startAngle: availabilityPie[0].startAngle,
endAngle: availabilityPie[0].endAngle,
startAngle: 0,
endAngle: angle,
innerRadius: this.outerRadius * this.donutWidthPercent,
outerRadius: this.outerRadius,
padAngle: 0
Expand All @@ -65,8 +88,8 @@ export class AvailabilityDonutController implements ng.IComponentController {
// Empty arc
{
path: this.arc({
startAngle: availabilityPie[1].startAngle,
endAngle: availabilityPie[1].endAngle,
startAngle: angle,
endAngle: Math.PI * 2,
innerRadius: this.outerRadius * this.donutWidthPercent,
outerRadius: this.outerRadius,
padAngle: 0
Expand All @@ -90,9 +113,45 @@ export class AvailabilityDonutController implements ng.IComponentController {
padAngle: 0
});

// Figure out rotations needed for target inflection markers
const radius = this.outerRadius + 5;
const tHeight = 8;
const tWidth = 7;

const targetArrowPath = `M 0 -${radius} L -${tWidth / 2} -${radius + tHeight} L ${tWidth / 2} -${radius + tHeight} L 0 -${radius}`;
const targetRotation = this.scaleTargets(this.targetNines);
const targetUnderRotation = this.scaleTargets(this.targetNines * 0.95);
const targetAngle = this.scaleTargets(this.targetNines, true) - (Math.PI / 2);
const targetUnderAngle = this.scaleTargets(this.targetNines * 0.95, true) - (Math.PI / 2);

const labelRadius = radius + tHeight + 3;
const targets: ITargetData[] = [
{
availability: this.targetNines,
rotation: targetRotation,
path: targetArrowPath,
labelPosition: [
labelRadius * Math.cos(targetAngle),
labelRadius * Math.sin(targetAngle)
],
score: 1
},
{
availability: (this.targetNines * 0.95),
rotation: targetUnderRotation,
path: targetArrowPath,
labelPosition: [
labelRadius * Math.cos(targetUnderAngle),
labelRadius * Math.sin(targetUnderAngle)
],
score: 2
}
];

return {
arcs: arcs,
total: total,
targets: targets,
width: this.outerRadius * 2.5,
height: this.outerRadius * 2.5
};
Expand Down
10 changes: 7 additions & 3 deletions app/scripts/modules/netflix/availability/availability.donut.html
@@ -1,12 +1,16 @@
<svg ng-attr-width="{{$ctrl.donut.width}}" ng-attr-height="{{$ctrl.donut.height}}">
<g ng-attr-transform="translate({{$ctrl.donut.width/2 || 0}},{{$ctrl.donut.height/2 || 0}})">
<svg ng-attr-width="{{$ctrl.donut.width+20}}" ng-attr-height="{{$ctrl.donut.height}}">
<g ng-attr-transform="translate({{($ctrl.donut.width+20)/2 || 0}},{{$ctrl.donut.height/2 || 0}})">
<g class="arc total">
<path ng-attr-d="{{$ctrl.donut.total}}" class="score-border"></path>
</g>
<g class="arc availability" ng-repeat="arc in $ctrl.donut.arcs">
<path ng-attr-d="{{arc.path}}" class="score-fill-{{arc.score}}"></path>
</g>
<g class="target" ng-repeat="target in $ctrl.donut.targets">
<path ng-attr-d="{{target.path}}" class="score-fill-{{target.score}}" ng-attr-transform="rotate({{target.rotation}})"></path>
<text class="target-value" alignment-baseline="middle" ng-attr-x="{{target.labelPosition[0]}}" ng-attr-y="{{target.labelPosition[1]}}">{{target.availability.toFixed(1)}}</text>
</g>
<text text-anchor="middle" alignment-baseline="baseline" x="0" y="0" style="font-size: {{$ctrl.ninesSize}}px" class="nines score-fill-{{$ctrl.score}}">{{$ctrl.displayNines}}</text>
<text text-anchor="middle" alignment-baseline="hanging" x="0" y="5" style="font-size: {{$ctrl.percentSize}}px" class="percent score-fill-{{$ctrl.score}}">{{$ctrl.availability}}%</text>
<text text-anchor="middle" alignment-baseline="hanging" x="0" y="5" style="font-size: {{$ctrl.percentSize}}px" class="percent score-fill-{{$ctrl.score}}">{{$ctrl.availability.toFixed(3)}}%</text>
</g>
</svg>

0 comments on commit dfc6d8a

Please sign in to comment.