Small UI tweaks for 0.3 prep (#377)

* Display more decimal points for truncated numbers, add hover info

* Filter completed pods out of web UI

* Decrease the polling interval from 10s to 2s

* Add more detailed pod categorization based on status

* Tweak filtering of pods, tweak explanations in status table
This commit is contained in:
Risha Mars 2018-02-19 14:11:03 -08:00 committed by GitHub
parent 1489a84316
commit 53354cf68f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 53 deletions

View File

@ -41,7 +41,7 @@ export default class DeploymentDetail extends React.Component {
let deployment = urlParams.get("deploy");
return {
lastUpdated: 0,
pollingInterval: 10000,
pollingInterval: 2000,
deploy: deployment,
pods: [],
upstreamMetrics: [],

View File

@ -17,7 +17,7 @@ export default class DeploymentsList extends React.Component {
this.loadFromServer = this.loadFromServer.bind(this);
this.state = {
pollingInterval: 10000, // TODO: poll based on metricsWindow size
pollingInterval: 2000, // TODO: poll based on metricsWindow size
metrics: [],
pendingRequests: false,
loaded: false,

View File

@ -1,7 +1,7 @@
import _ from 'lodash';
import { metricToFormatter } from './util/Utils.js';
import React from 'react';
import { Table } from 'antd';
import { Table, Tooltip } from 'antd';
/*
Table to display Success Rate, Requests and Latency in tabs.
@ -15,6 +15,16 @@ const resourceInfo = {
"path": { title: "path", url: null }
};
const withTooltip = (d, metricName) => {
return (
<Tooltip
title={metricToFormatter["UNTRUNCATED"](d)}
overlayStyle={{ fontSize: "12px" }}>
<span>{metricToFormatter[metricName](d)}</span>
</Tooltip>
);
};
const columnDefinitions = (sortable = true, resource, ConduitLink) => {
return [
{
@ -33,7 +43,7 @@ const columnDefinitions = (sortable = true, resource, ConduitLink) => {
key: "requestRateRollup",
className: "numeric",
sorter: sortable ? (a, b) => numericSort(a.requestRate, b.requestRate) : false,
render: d => metricToFormatter["REQUEST_RATE"](d)
render: d => withTooltip(d, "REQUEST_RATE")
},
{
title: "Success Rate",

View File

@ -10,7 +10,11 @@ import React from 'react';
import { rowGutter } from './util/Utils.js';
import StatusTable from './StatusTable.jsx';
import { Col, Row, Table } from 'antd';
import { processRollupMetrics, processTimeseriesMetrics } from './util/MetricUtils.js';
import {
getPodsByDeployment,
processRollupMetrics,
processTimeseriesMetrics
} from './util/MetricUtils.js';
import './../../css/service-mesh.css';
const serviceMeshDetailsColumns = [
@ -99,14 +103,14 @@ export default class ServiceMesh extends React.Component {
.then(([metrics, ts, pods]) => {
let m = processRollupMetrics(metrics.metrics, "component");
let tsByComponent = processTimeseriesMetrics(ts.metrics, "component");
let d = this.getDeploymentList(pods.pods);
let c = this.processComponents(pods.pods);
let podsByDeploy = getPodsByDeployment(pods.pods);
let controlPlanePods = this.processComponents(pods.pods);
this.setState({
metrics: m,
timeseriesByComponent: tsByComponent,
deploys: d,
components: c,
deploys: podsByDeploy,
components: controlPlanePods,
lastUpdated: Date.now(),
pendingRequests: false,
loaded: true,
@ -124,7 +128,7 @@ export default class ServiceMesh extends React.Component {
addedDeploymentCount() {
return _.size(_.filter(this.state.deploys, d => {
return _.every(d.pods, ["value", "good"]);
return _.every(d.pods, ["added", true]);
}));
}
@ -157,23 +161,6 @@ export default class ServiceMesh extends React.Component {
];
}
getDeploymentList(pods) {
return _(pods)
.reject(p => _.isEmpty(p.deployment) || p.controlPlane)
.groupBy("deployment")
.map((componentPods, name) => {
_.remove(componentPods, p => {
return p.status === "Terminating";
});
let podStatuses = _.map(componentPods, p => {
return { name: p.name, value: p.added ? "good" : "neutral" };
});
return { name: name, pods: _.sortBy(podStatuses, "name") };
})
.sortBy("name")
.value();
}
processComponents(pods) {
let podIndex = _(pods)
.filter(p => p.controlPlane)

View File

@ -6,17 +6,20 @@ const columnConfig = {
"Pod Status": {
width: 200,
wrapDotsAt: 7, // dots take up more than one line in the table; space them out
dotExplanation: {
good: "is up and running",
neutral: "has not been started"
dotExplanation: status => {
return status.value === "good" ? "is up and running" : "has not been started";
}
},
"Proxy Status": {
width: 250,
wrapDotsAt: 9,
dotExplanation: {
good: "has been added to the mesh",
neutral: "has not been added to the mesh"
dotExplanation: pod => {
let addedStatus = !pod.added ? "Not in mesh" : "Added to mesh";
return (<React.Fragment>
<div>Pod status: {pod.status}</div>
<div>{addedStatus}</div>
</React.Fragment>);
}
}
};
@ -26,7 +29,7 @@ const StatusDot = ({status, multilineDots, columnName}) => (
placement="top"
title={<div>
<div>{status.name}</div>
<div>{_.get(columnConfig, [columnName, "dotExplanation", status.value])}</div>
<div>{_.get(columnConfig, [columnName, "dotExplanation"])(status)}</div>
</div>}
overlayStyle={{ fontSize: "12px" }}>
<div

View File

@ -26,17 +26,36 @@ const convertLatencyTs = rawTs => {
return _.groupBy(latencies, 'label');
};
const getPodCategorization = pod => {
if (pod.added && pod.status === "Running") {
return "good";
} else if (pod.status === "Pending" || pod.status === "Running") {
return "neutral";
} else if (pod.status === "Failed") {
return "bad";
}
return ""; // Terminating | Succeeded | Unknown
};
export const getPodsByDeployment = pods => {
return _(pods)
.reject(p => _.isEmpty(p.deployment) || p.controlPlane)
.groupBy('deployment')
.map((componentPods, name) => {
let podsWithStatus = _.chain(componentPods)
.map(p => {
return _.merge({}, p, { value: getPodCategorization(p) });
})
.reject(p => _.isEmpty(p.value))
.value();
return {
name: name,
added: _.every(componentPods, 'added'),
pods: componentPods
pods: _.sortBy(podsWithStatus, 'name')
};
})
.reject(p => _.isEmpty(p.pods))
.sortBy('name')
.value();
};

View File

@ -27,7 +27,8 @@ const formatLatency = m => {
export const metricToFormatter = {
"REQUEST_RATE": m => _.isNil(m) ? "---" : styleNum(m, " RPS", true),
"SUCCESS_RATE": m => _.isNil(m) ? "---" : successRateFormatter(m),
"LATENCY": formatLatency
"LATENCY": formatLatency,
"UNTRUNCATED": m => styleNum(m, "", false)
};
/*
@ -61,13 +62,13 @@ export const styleNum = (number, unit = "", truncate = true) => {
}
if (truncate && number > 999999999) {
number = roundNumber(number / 1000000000.0, 1);
number = roundNumber(number / 1000000000.0, 3);
return addCommas(number) + "G" + unit;
} else if (truncate && number > 999999) {
number = roundNumber(number / 1000000.0, 1);
number = roundNumber(number / 1000000.0, 3);
return addCommas(number) + "M" + unit;
} else if (truncate && number > 999) {
number = roundNumber(number / 1000.0, 1);
number = roundNumber(number / 1000.0, 3);
return addCommas(number) + "k" + unit;
} else if (number > 999) {
number = roundNumber(number, 0);

View File

@ -67,7 +67,7 @@ describe('ServiceMesh', () => {
it("renders controller component summaries", () => {
let addedPods = _.cloneDeep(podFixtures.pods);
_.set(addedPods[0], "added", true);
_.set(addedPods[1], "added", true);
fetchStub.resolves({
ok: true,

View File

@ -17,16 +17,16 @@ describe('Utils', () => {
compare( 5.0000001, "5" );
compare( 7.6666667, "7.67" );
compare( 123.456, "123.46" );
compare( 1212.999999, "1.2k" );
compare( 5329.333333, "5.3k" );
compare( 16384.888, "16.4k" );
compare( 131042, "131k" );
compare( 1048576, "1M" );
compare( 2097152.1, "2.1M" );
compare( 16777216, "16.8M" );
compare( 536870912, "536.9M" );
compare( 1073741824, "1.1G" );
compare(68719476736, "68.7G" );
compare( 1212.999999, "1.213k" );
compare( 5329.333333, "5.329k" );
compare( 16384.888, "16.385k" );
compare( 131042, "131.042k");
compare( 1048576, "1.049M" );
compare( 2097152.1, "2.097M" );
compare( 16777216, "16.777M" );
compare( 536870912, "536.871M");
compare( 1073741824, "1.074G" );
compare(68719476736, "68.719G" );
});
it('properly formats numbers with units and no truncation', () => {
@ -63,9 +63,9 @@ describe('Utils', () => {
expect(metricToFormatter["REQUEST_RATE"](99)).to.equal('99 RPS');
expect(metricToFormatter["REQUEST_RATE"](999)).to.equal('999 RPS');
expect(metricToFormatter["REQUEST_RATE"](1000)).to.equal('1k RPS');
expect(metricToFormatter["REQUEST_RATE"](4444)).to.equal('4.4k RPS');
expect(metricToFormatter["REQUEST_RATE"](9999)).to.equal('10k RPS');
expect(metricToFormatter["REQUEST_RATE"](99999)).to.equal('100k RPS');
expect(metricToFormatter["REQUEST_RATE"](4444)).to.equal('4.444k RPS');
expect(metricToFormatter["REQUEST_RATE"](9999)).to.equal('9.999k RPS');
expect(metricToFormatter["REQUEST_RATE"](99999)).to.equal('99.999k RPS');
});
it('formats subsecond latency as ms', () => {