Start tweaking the look and feel of the Octopus graph (#1501)

Do a little more work to get the octopus graph closer to the mocks.
This version gives you a slightly better navigational sense of where 
you are in the app, and gives you a clearer
view of the neighbouring stats
This commit is contained in:
Risha Mars 2018-08-22 10:43:27 -07:00 committed by GitHub
parent cc98b5e784
commit 062d35db7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 84 deletions

View File

@ -1,40 +1,51 @@
@import 'styles.css'; @import 'styles.css';
.octopus-container { .octopus-container {
padding: 80px; padding: calc(var(--base-width) * 4);
} }
.octopus-graph { .octopus-graph {
background-color: #ffffff; & .octopus-body {
height: 100%; background-color: white;
width: 600px; margin-bottom: calc(var(--base-width) * 3);
margin-left: auto;
margin-right: auto;
padding: 24px;
box-shadow: 2px 2px 2px var(--neutralgrey); box-shadow: 2px 2px 2px var(--neutralgrey);
}
& .octopus-title, & .octopus-metric { & .octopus-col {
text-align: center; text-align: center;
} }
& .resource-col {
background-color: #FAFAFA;
padding: 32px;
}
& .octopus-upstreams .neighbor, & .octopus-downstreams .neighbor { & .octopus-title {
clear: both; padding: 8px 0;
& .status-dot { &.main {
margin: 4px 4px 0 4px; font-size: 24px;
font-weight: var(--font-weight-extra-bold);
}
&.neighbor {
font-size: 16px;
font-weight: var(--font-weight-bold);
} }
} }
& .octopus-upstreams .neighbor > div div { & .octopus-metric {
float: left;
}
& .octopus-downstreams .neighbor > div div {
float: right;
}
}
.octopus-metric-lg {
text-align: center;
line-height: 32px; line-height: 32px;
&.status-good {
color: var(--green);
}
&.status-poor {
color: var(--siennared);
}
&.status-neutral {
color: #E0E0E0;
}
&.status-ok {
color: #FF8C00;
}
}
} }

View File

@ -1,95 +1,89 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Col, Popover, Row } from 'antd'; import { Col, Icon, Row } from 'antd';
import { metricToFormatter, toShortResourceName } from './util/Utils.js'; import { metricToFormatter, toShortResourceName } from './util/Utils.js';
import './../../css/octopus.css'; import './../../css/octopus.css';
const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`; const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;
const getDotClassification = sr => { const getSrClassification = sr => {
if (sr < 0.9) { if (sr < 0.9) {
return "status-dot-poor"; return "status-poor";
} else if (sr < 0.95) { } else if (sr < 0.95) {
return "status-dot-ok"; return "status-ok";
} else {return "status-dot-good";} } else {return "status-good";}
}; };
const Neighbor = ({neighbor, direction}) => { const Metric = ({title, value, className}) => {
return ( return (
<div className="neighbor"> <Row type="flex" justify="center" className={`octopus-metric ${className}`}>
<Popover
title={displayName(neighbor)}
content={<MetricSummaryRow resource={neighbor} metricClass="metric-sm" />}
placement={direction ==="in" ? "left" : "right"}>
<div className="neighbor-row">
<div>{direction === "in" ? "<" : ">"}</div>
<div className={`status-dot ${getDotClassification(neighbor.successRate)}`} />
<div>{displayName(neighbor)}</div>
</div>
</Popover>
</div>
);
};
Neighbor.propTypes = {
direction: PropTypes.string.isRequired,
neighbor: PropTypes.shape({}).isRequired
};
const Metric = ({title, value, metricClass}) => {
return (
<Row type="flex" justify="center" className={`octopus-${metricClass}`}>
<Col span={12} className="octopus-metric-title"><div>{title}</div></Col> <Col span={12} className="octopus-metric-title"><div>{title}</div></Col>
<Col span={12} className="octopus-metric-value"><div>{value}</div></Col> <Col span={12} className="octopus-metric-value"><div>{value}</div></Col>
</Row> </Row>
); );
}; };
Metric.defaultProps = { className: "" };
Metric.propTypes = { Metric.propTypes = {
metricClass: PropTypes.string.isRequired, className: PropTypes.string,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
value: PropTypes.string.isRequired value: PropTypes.string.isRequired
}; };
const MetricSummaryRow = ({resource, metricClass}) => { const ArrowCol = ({showArrow}) => <Col span={2} className="octopus-col">{!showArrow ? " " : <Icon type="arrow-right" />}</Col>;
return ( ArrowCol.propTypes = PropTypes.bool.isRequired;
<React.Fragment>
<Metric title="Success Rate" value={metricToFormatter["SUCCESS_RATE"](resource.successRate)} metricClass={metricClass} />
<Metric title="Request Rate" value={metricToFormatter["REQUEST_RATE"](resource.requestRate)} metricClass={metricClass} />
<Metric title="P99 Latency" value={metricToFormatter["LATENCY"](_.get(resource, "latency.P99"))} metricClass={metricClass} />
</React.Fragment>
);
};
MetricSummaryRow.propTypes = {
metricClass: PropTypes.string.isRequired,
resource: PropTypes.shape({}).isRequired
};
export default class Octopus extends React.Component { export default class Octopus extends React.Component {
static defaultProps = { static defaultProps = {
metrics: {}, neighbors: {},
neighbors: {} resource: {}
} }
static propTypes = { static propTypes = {
metrics: PropTypes.shape({}),
neighbors: PropTypes.shape({}), neighbors: PropTypes.shape({}),
resource: PropTypes.shape({}).isRequired resource: PropTypes.shape({})
}
renderResourceSummary(resource, cn) {
return (
<div key={resource.name} className={`octopus-body ${cn}`}>
<div className={`octopus-title ${cn} ${getSrClassification(resource.successRate)}`}>
<this.props.api.PrefixedLink to={`/namespaces/${resource.namespace}/${resource.type}s/${resource.name}`}>
{displayName(resource)}
</this.props.api.PrefixedLink>
</div>
<Metric
title="SR"
className={`${getSrClassification(resource.successRate)}`}
value={metricToFormatter["SUCCESS_RATE"](resource.successRate)} />
<Metric title="RPS" value={metricToFormatter["REQUEST_RATE"](resource.requestRate)} />
<Metric title="P99" value={metricToFormatter["LATENCY"](_.get(resource, "latency.P99"))} />
</div>
);
} }
render() { render() {
let { resource, metrics, neighbors } = this.props; let { resource, neighbors } = this.props;
let hasUpstreams = _.size(neighbors.upstream) > 0;
let hasDownstreams = _.size(neighbors.downstream) > 0;
return ( return (
<div className="octopus-container"> <div className="octopus-container">
<div className="octopus-graph"> <div className="octopus-graph">
<h1 className="octopus-title">{displayName(resource)}</h1> <Row type="flex" justify="center" gutter={32} align="middle">
<MetricSummaryRow resource={metrics} metricClass="metric-lg" /> <Col span={6} className={`octopus-col ${hasUpstreams ? "resource-col" : ""}`}>
<hr /> {_.map(neighbors.upstream, n => this.renderResourceSummary(n, "neighbor"))}
<Row type="flex" justify="center">
<Col span={12} className="octopus-upstreams">
{_.map(neighbors.upstream, n => <Neighbor neighbor={n} direction="in" key={n.namespace + "-" + n.name} />)}
</Col> </Col>
<Col span={12} className="octopus-downstreams">
{_.map(neighbors.downstream, n => <Neighbor neighbor={n} direction="out" key={n.namespace + "-" + n.name} />)} <ArrowCol showArrow={hasUpstreams} />
<Col span={8} className="octopus-col resource-col">
{this.renderResourceSummary(resource, "main")}
</Col>
<ArrowCol showArrow={hasDownstreams} />
<Col span={6} className={`octopus-col ${hasDownstreams ? "resource-col" : ""}`}>
{_.map(neighbors.downstream, n => this.renderResourceSummary(n, "neighbor"))}
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -54,6 +54,10 @@ export class ResourceDetailBase extends React.Component {
pollingInterval: 2000, pollingInterval: 2000,
resourceMetrics: [], resourceMetrics: [],
podMetrics: [], // metrics for all pods whose owner is this resource podMetrics: [], // metrics for all pods whose owner is this resource
neighborMetrics: {
upstream: {},
downstream: {}
},
pendingRequests: false, pendingRequests: false,
loaded: false, loaded: false,
error: null error: null
@ -170,9 +174,9 @@ export class ResourceDetailBase extends React.Component {
<div> <div>
<div className="page-section"> <div className="page-section">
<Octopus <Octopus
resource={this.state.resource} resource={this.state.resourceMetrics[0]}
metrics={this.state.resourceMetrics[0]} neighbors={this.state.neighborMetrics}
neighbors={this.state.neighborMetrics} /> api={this.api} />
</div> </div>
{ _.isEmpty(this.state.neighborMetrics.upstream) ? null : ( { _.isEmpty(this.state.neighborMetrics.upstream) ? null : (